J'écris un projet dans Django et je vois que 80% du code est dans le fichier models.py
. Ce code prête à confusion et, après un certain temps, je cesse de comprendre ce qui se passe réellement.
Voici ce qui me dérange:
- Je trouve moche que mon niveau de modèle (qui était censé être uniquement responsable du travail avec les données d'une base de données) envoie également des e-mails, marche sur l'API vers d'autres services, etc.
- De plus, je trouve inacceptable de placer la logique métier dans la vue, car de cette façon, il devient difficile à contrôler. Par exemple, dans mon application, il existe au moins trois façons de créer de nouvelles instances de
User
, mais techniquement, il doit les créer de manière uniforme. - Je ne remarque pas toujours quand les méthodes et propriétés de mes modèles deviennent non déterministes et quand elles développent des effets secondaires.
Voici un exemple simple. Au début, le User
modèle était comme ceci:
class User(db.Models):
def get_present_name(self):
return self.name or 'Anonymous'
def activate(self):
self.status = 'activated'
self.save()
Au fil du temps, cela s'est transformé en ceci:
class User(db.Models):
def get_present_name(self):
# property became non-deterministic in terms of database
# data is taken from another service by api
return remote_api.request_user_name(self.uid) or 'Anonymous'
def activate(self):
# method now has a side effect (send message to user)
self.status = 'activated'
self.save()
send_mail('Your account is activated!', '…', [self.email])
Ce que je veux, c'est séparer les entités dans mon code:
- Entités de ma base de données, niveau base de données: que contient mon application?
- Entités de mon application, niveau logique métier: que peut faire mon application?
Quelles sont les bonnes pratiques pour mettre en œuvre une telle approche qui peut être appliquée dans Django?
Réponses:
Il semble que vous posiez des questions sur la différence entre le modèle de données et le modèle de domaine - ce dernier est l'endroit où vous pouvez trouver la logique métier et les entités telles que perçues par votre utilisateur final, le premier est l'endroit où vous stockez réellement vos données.
De plus, j'ai interprété la troisième partie de votre question comme: comment remarquer l'échec de garder ces modèles séparés.
Ce sont deux concepts très différents et il est toujours difficile de les séparer. Cependant, certains modèles et outils communs peuvent être utilisés à cette fin.
À propos du modèle de domaine
La première chose que vous devez reconnaître est que votre modèle de domaine ne concerne pas vraiment les données; il s'agit d' actions et de questions telles que "activer cet utilisateur", "désactiver cet utilisateur", "quels utilisateurs sont actuellement activés?" et "quel est le nom de cet utilisateur?". En termes classiques: il s'agit de requêtes et de commandes .
Penser en commandes
Commençons par regarder les commandes de votre exemple: "activer cet utilisateur" et "désactiver cet utilisateur". La bonne chose à propos des commandes est qu'elles peuvent facilement être exprimées par de petits scénarios donnés quand ils sont alors:
De tels scénarios sont utiles pour voir comment différentes parties de votre infrastructure peuvent être affectées par une seule commande - dans ce cas, votre base de données (une sorte d'indicateur `` actif ''), votre serveur de messagerie, votre journal système, etc.
De tels scénarios vous aident également à configurer un environnement de développement piloté par les tests.
Et enfin, la réflexion dans les commandes vous aide vraiment à créer une application orientée tâche. Vos utilisateurs l'apprécieront :-)
Exprimer des commandes
Django propose deux façons simples d'exprimer des commandes; ce sont deux options valables et il n'est pas rare de mélanger les deux approches.
La couche service
Le module de service a déjà été décrit par @Hedde . Ici, vous définissez un module séparé et chaque commande est représentée comme une fonction.
services.py
Utilisation de formulaires
L'autre façon est d'utiliser un formulaire Django pour chaque commande. Je préfère cette approche, car elle combine plusieurs aspects étroitement liés:
forms.py
Penser dans les requêtes
Votre exemple ne contenait aucune requête, j'ai donc pris la liberté de créer quelques requêtes utiles. Je préfère utiliser le terme «question», mais les requêtes sont la terminologie classique. Les requêtes intéressantes sont: "Quel est le nom de cet utilisateur?", "Cet utilisateur peut-il se connecter?", "Me montrer une liste d'utilisateurs désactivés" et "Quelle est la répartition géographique des utilisateurs désactivés?"
Avant de vous lancer dans la réponse à ces requêtes, vous devez toujours vous poser deux questions: s'agit-il d'une requête de présentation uniquement pour mes modèles, et / ou d'une requête de logique métier liée à l'exécution de mes commandes, et / ou d'une requête de rapport .
Les requêtes de présentation sont simplement faites pour améliorer l'interface utilisateur. Les réponses aux requêtes de logique métier affectent directement l'exécution de vos commandes. Les requêtes de rapport sont uniquement à des fins analytiques et ont des contraintes de temps plus lâches. Ces catégories ne s'excluent pas mutuellement.
L'autre question est: "ai-je un contrôle total sur les réponses?" Par exemple, lorsque nous demandons le nom de l'utilisateur (dans ce contexte), nous n'avons aucun contrôle sur le résultat, car nous nous appuyons sur une API externe.
Faire des requêtes
La requête la plus élémentaire dans Django est l'utilisation de l'objet Manager:
Bien sûr, cela ne fonctionne que si les données sont réellement représentées dans votre modèle de données. Ce n'est pas toujours le cas. Dans ces cas, vous pouvez considérer les options ci-dessous.
Balises et filtres personnalisés
La première alternative est utile pour les requêtes qui sont simplement de présentation: balises personnalisées et filtres de modèle.
template.html
template_tags.py
Méthodes de requête
Si votre requête n'est pas simplement de présentation, vous pouvez ajouter des requêtes à votre services.py (si vous l'utilisez), ou introduire un module queries.py :
queries.py
Modèles de proxy
Les modèles de proxy sont très utiles dans le contexte de la logique métier et du reporting. Vous définissez essentiellement un sous-ensemble amélioré de votre modèle. Vous pouvez remplacer le QuerySet de base d'un gestionnaire en remplaçant la
Manager.get_queryset()
méthode.models.py
Modèles de requête
Pour les requêtes qui sont intrinsèquement complexes, mais qui sont exécutées assez souvent, il existe la possibilité de modèles de requête. Un modèle de requête est une forme de dénormalisation dans laquelle les données pertinentes pour une seule requête sont stockées dans un modèle distinct. L'astuce est bien sûr de synchroniser le modèle dénormalisé avec le modèle principal. Les modèles de requête ne peuvent être utilisés que si les modifications sont entièrement sous votre contrôle.
models.py
La première option consiste à mettre à jour ces modèles dans vos commandes. Ceci est très utile si ces modèles ne sont modifiés que par une ou deux commandes.
forms.py
Une meilleure option serait d'utiliser des signaux personnalisés. Ces signaux sont bien entendu émis par vos commandes. Les signaux ont l'avantage de pouvoir synchroniser plusieurs modèles de requête avec votre modèle d'origine. De plus, le traitement du signal peut être déchargé sur des tâches d'arrière-plan, en utilisant Celery ou des cadres similaires.
signaux.py
forms.py
models.py
Le garder propre
Lorsque vous utilisez cette approche, il devient ridiculement facile de déterminer si votre code reste propre. Suivez simplement ces directives:
Il en va de même pour les vues (car les vues souffrent souvent du même problème).
Quelques références
Documentation Django: modèles proxy
Documentation Django: signaux
Architecture: conception pilotée par domaine
la source
User.objects.inactive_users()
. Mais l'exemple de modèle proxy ici IMO conduit à une sémantique incorrecte:u = InactiveUser.objects.all()[0]; u.active = True; u.save()
et pourtantisinstance(u, InactiveUser) == True
. Je mentionnerais également un moyen efficace de maintenir un modèle de requête dans de nombreux cas avec une vue db.J'implémente généralement une couche de service entre les vues et les modèles. Cela agit comme l'API de votre projet et vous donne une bonne vue d'hélicoptère de ce qui se passe. J'ai hérité de cette pratique d'un de mes collègues qui utilise beaucoup cette technique de superposition avec les projets Java (JSF), par exemple:
models.py
services.py
views.py
la source
Tout d'abord, ne vous répétez pas .
Ensuite, veuillez faire attention à ne pas trop concevoir, parfois c'est juste une perte de temps et cela fait perdre à quelqu'un l'attention sur ce qui est important. Passez en revue le zen du python de temps en temps.
Jetez un œil aux projets actifs
le référentiel de matrice est également une bonne chose à regarder.
yourapp/models/logicalgroup.py
User
,Group
et les modèles connexes peuvent passer sousyourapp/models/users.py
Poll
,Question
,Answer
... pourrait passer sousyourapp/models/polls.py
__all__
intérieuryourapp/models/__init__.py
En savoir plus sur MVC
request.GET
/request.POST
... etctastypie
oupiston
Profitez des middleware / templatetags
Profitez des gestionnaires de modèles
User
peut aller dans unUserManager(models.Manager)
.models.Model
.queryset
pourrait aller dans unmodels.Manager
.User
à la fois, vous pouvez donc penser qu'il devrait vivre sur le modèle lui-même, mais lors de la création de l'objet, vous n'avez probablement pas tous les détails:Exemple:
Utilisez des formulaires dans la mesure du possible
Un grand nombre de codes passe-partout peuvent être éliminés si vous avez des formulaires qui correspondent à un modèle. C'est
ModelForm documentation
assez bon. La séparation du code des formulaires du code du modèle peut être utile si vous avez beaucoup de personnalisation (ou évitez parfois les erreurs d'importation cycliques pour des utilisations plus avancées).Utilisez les commandes de gestion lorsque cela est possible
yourapp/management/commands/createsuperuser.py
yourapp/management/commands/activateinbulk.py
si vous avez une logique métier, vous pouvez la séparer
django.contrib.auth
utilise des backends , tout comme db a un backend ... etc.setting
pour votre logique métier (par exempleAUTHENTICATION_BACKENDS
)django.contrib.auth.backends.RemoteUserBackend
yourapp.backends.remote_api.RemoteUserBackend
yourapp.backends.memcached.RemoteUserBackend
exemple de backend:
pourrait devenir:
en savoir plus sur les modèles de conception
en savoir plus sur les limites de l'interface
yourapp.models
yourapp.vendor
yourapp.libs
yourapp.libs.vendor
ouyourapp.vendor.libs
Bref, vous auriez pu
yourapp/core/backends.py
yourapp/core/models/__init__.py
yourapp/core/models/users.py
yourapp/core/models/questions.py
yourapp/core/backends.py
yourapp/core/forms.py
yourapp/core/handlers.py
yourapp/core/management/commands/__init__.py
yourapp/core/management/commands/closepolls.py
yourapp/core/management/commands/removeduplicates.py
yourapp/core/middleware.py
yourapp/core/signals.py
yourapp/core/templatetags/__init__.py
yourapp/core/templatetags/polls_extras.py
yourapp/core/views/__init__.py
yourapp/core/views/users.py
yourapp/core/views/questions.py
yourapp/core/signals.py
yourapp/lib/utils.py
yourapp/lib/textanalysis.py
yourapp/lib/ratings.py
yourapp/vendor/backends.py
yourapp/vendor/morebusinesslogic.py
yourapp/vendor/handlers.py
yourapp/vendor/middleware.py
yourapp/vendor/signals.py
yourapp/tests/test_polls.py
yourapp/tests/test_questions.py
yourapp/tests/test_duplicates.py
yourapp/tests/test_ratings.py
ou toute autre chose qui vous aide; trouver les interfaces dont vous avez besoin et les limites vous aidera.
la source
Django utilise un type de MVC légèrement modifié. Il n'y a pas de concept de "contrôleur" dans Django. Le proxy le plus proche est une "vue", ce qui a tendance à créer de la confusion avec les convertis MVC car dans MVC une vue ressemble plus au "modèle" de Django.
Dans Django, un "modèle" n'est pas simplement une abstraction de base de données. À certains égards, il partage son devoir avec la «vision» de Django en tant que contrôleur de MVC. Il contient l'intégralité du comportement associé à une instance. Si cette instance doit interagir avec une API externe dans le cadre de son comportement, c'est toujours du code de modèle. En fait, les modèles ne sont pas du tout tenus d'interagir avec la base de données, vous pouvez donc concevoir des modèles qui existent entièrement en tant que couche interactive pour une API externe. C'est un concept beaucoup plus libre d'un "modèle".
la source
À Django, la structure MVC est, comme l'a dit Chris Pratt, différente du modèle MVC classique utilisé dans d'autres cadres, je pense que la principale raison pour cela est d'éviter une structure d'application trop stricte, comme cela se produit dans d'autres cadres MVC comme CakePHP.
Dans Django, MVC a été implémenté de la manière suivante:
Le calque de vue est divisé en deux. Les vues ne doivent être utilisées que pour gérer les requêtes HTTP, elles sont appelées et y répondent. Les vues communiquent avec le reste de votre application (formulaires, formulaires, classes personnalisées, ou dans des cas simples directement avec les modèles). Pour créer l'interface, nous utilisons des modèles. Les modèles sont de type chaîne pour Django, il y mappe un contexte, et ce contexte a été communiqué à la vue par l'application (lorsque la vue le demande).
La couche modèle donne l'encapsulation, l'abstraction, la validation, l'intelligence et rend vos données orientées objet (ils disent qu'un jour le SGBD le fera également). Cela ne signifie pas que vous devez créer d'énormes fichiers models.py (en fait, un très bon conseil est de diviser vos modèles en différents fichiers, de les placer dans un dossier appelé 'models', de créer un fichier '__init__.py' dans ce dossier dans lequel vous importez tous vos modèles et enfin utilisez l'attribut 'app_label' de models.Model class). Le modèle devrait vous empêcher d'utiliser des données, il simplifiera votre application. Vous devez également, si nécessaire, créer des classes externes, comme des «outils» pour vos modèles. Vous pouvez également utiliser l'héritage dans les modèles, en définissant l'attribut «abstrait» de la méta-classe de votre modèle sur «True».
Où est le reste? Eh bien, les petites applications Web sont généralement une sorte d'interface avec les données, dans certains petits cas de programme, utiliser des vues pour interroger ou insérer des données serait suffisant. Les cas les plus courants utiliseront des formulaires ou des formulaires, qui sont en fait des "contrôleurs". Ce n'est autre qu'une solution pratique à un problème commun, et très rapide. C'est ce que fait un site Web.
Si les formulaires ne vous conviennent pas, vous devez créer vos propres classes pour faire la magie, un très bon exemple de ceci est l'application d'administration: vous pouvez lire le code ModelAmin, cela fonctionne en fait comme un contrôleur. Il n'y a pas de structure standard, je vous suggère d'examiner les applications Django existantes, cela dépend de chaque cas. C'est ce que les développeurs Django voulaient, vous pouvez ajouter une classe d'analyseur XML, une classe de connecteur API, ajouter Celery pour effectuer des tâches, tordu pour une application basée sur un réacteur, utiliser uniquement l'ORM, créer un service Web, modifier l'application d'administration et plus encore. .. Il est de votre responsabilité de créer du code de bonne qualité, de respecter ou non la philosophie MVC, de le faire basé sur des modules et de créer vos propres couches d'abstraction. C'est très flexible.
Mon conseil: lisez autant de code que possible, il y a beaucoup d'applications django, mais ne les prenez pas si au sérieux. Chaque cas est différent, les modèles et la théorie sont utiles, mais pas toujours, c'est une science imprécise, Django vous fournit juste de bons outils que vous pouvez utiliser pour atténuer certaines douleurs (comme l'interface d'administration, la validation de formulaire Web, i18n, la mise en œuvre de modèles d'observateurs, tous) les précédents et d'autres), mais les bons designs proviennent de designers expérimentés.
PS: utilisez la classe 'User' de l'application auth (du django standard), vous pouvez faire par exemple des profils utilisateurs, ou au moins lire son code, cela sera utile pour votre cas.
la source
Une vieille question, mais j'aimerais quand même proposer ma solution. Il est basé sur l'acceptation du fait que les objets de modèle nécessitent également des fonctionnalités supplémentaires alors qu'il est difficile de les placer dans models.py . La logique métier lourde peut être écrite séparément selon les goûts personnels, mais j'aime au moins le modèle pour faire tout ce qui concerne lui-même. Cette solution prend également en charge ceux qui aiment que toute la logique soit placée dans les modèles eux-mêmes.
En tant que tel, j'ai conçu un hack qui me permet de séparer la logique des définitions de modèle et d'obtenir toujours toutes les indications de mon IDE.
Les avantages devraient être évidents, mais cela en énumère quelques-uns que j'ai observés:
J'utilise ceci avec Python 3.4 et supérieur et Django 1.8 et supérieur.
app / models.py
app / logic / user.py
La seule chose que je ne peux pas comprendre, c'est comment faire en sorte que mon IDE (PyCharm dans ce cas) reconnaisse que UserLogic est en fait un modèle utilisateur. Mais comme il s'agit évidemment d'un hack, je suis assez heureux d'accepter la petite nuisance de toujours spécifier le type de
self
paramètre.la source
Je devrais être d'accord avec vous. Il y a beaucoup de possibilités dans django mais le meilleur endroit pour commencer est de revoir la philosophie de conception de Django .
L'appel d'une API à partir d'une propriété de modèle ne serait pas idéal, il semble qu'il serait plus logique de faire quelque chose comme ça dans la vue et éventuellement de créer une couche de service pour garder les choses au sec. Si l'appel à l'API n'est pas bloquant et que l'appel est coûteux, l'envoi de la demande à un technicien de service (un technicien qui consomme à partir d'une file d'attente) peut avoir un sens.
Selon la philosophie de conception de Django, les modèles englobent tous les aspects d'un "objet". Donc, toute logique métier liée à cet objet devrait y vivre:
Les effets secondaires que vous décrivez sont évidents, la logique ici pourrait être mieux décomposée en ensembles de requêtes et gestionnaires. Voici un exemple:
models.py
admin.py
la source
Je suis principalement d'accord avec la réponse choisie ( https://stackoverflow.com/a/12857584/871392 ), mais je veux ajouter une option dans la section Faire des requêtes.
On peut définir des classes QuerySet pour les modèles pour effectuer des requêtes de filtre et ainsi de suite. Après cela, vous pouvez proxy cette classe d'ensemble de requêtes pour le gestionnaire de modèle, comme le font les classes Manager et QuerySet intégrées.
Bien que, si vous deviez interroger plusieurs modèles de données pour obtenir un modèle de domaine, il me semble plus raisonnable de le mettre dans un module séparé comme suggéré précédemment.
la source
Article le plus complet sur les différentes options avec des avantages et des inconvénients:
Source: https://sunscrapers.com/blog/where-to-put-business-logic-django/
la source
Django est conçu pour être facilement utilisé pour diffuser des pages Web. Si vous n'êtes pas à l'aise avec cela, vous devriez peut-être utiliser une autre solution.
J'écris la racine ou les opérations courantes sur le modèle (pour avoir la même interface) et les autres sur le contrôleur du modèle. Si j'ai besoin d'une opération d'un autre modèle, j'importe son contrôleur.
Cette approche me suffit et la complexité de mes applications.
La réponse de Hedde est un exemple qui montre la flexibilité de django et de python lui-même.
Question très intéressante quand même!
la source