Comment combattre la complexité logicielle d'un monolithe Django qui grossit ?
En encapsulant cette complexité plutôt que de l'étaler partout (vues, sérialiseurs, formulaires, gabarits etc.).
Comment encapsuler ? Eh bien ! C'est un point de vue subjectif.
Certains mettent en place des service layers.
James Bennett a des arguments contre eux dans Against service layers in Django et More on service layers in Django parmi lesquels une mise en relief du caractère central et non-amovible de l'ORM dans Django :
And in Django-based applications it's even less likely to try to swap out the ORM without other huge code changes happening at the same time, because the Django ORM is probably the single most tightly-integrated component of the entire framework — if you stop using it, you're throwing away so much other stuff that “why are we even still using Django” becomes a really significant question.
Pour moi, une application Django bien conçue doit encapsuler la complexité métier avec l'ORM.
Model
, Manager
et QuerySet
L'ORM permet d'exposer des APIs par le truchement des classes Model
, Manager
et QuerySet
.
La classe Model
implémente le motif Active Record. La classe représente une table de la base de données, tandis qu'une instance de cette classe représente une ligne d'une table de la base de données.
Au minimum, un Manager
et un QuerySet
sont associés à chaque Model
. Ces trois classes sont intimement liées et leur combinaison (avec une touche de métaclasse) offre une abstraction de base de données permettant de créer, obtenir, mettre à jour et supprimer des objets.
La classe QuerySet
porte les opérations CRUD.
La classe Manager
est surtout un proxy entre un Model
et un QuerySet
. La quasi-totalité de l'API QuerySet
est exposée par l'intermédiaire d'un Manager
dont le nom est objects
par défaut :
all_entries = Entry.objects.all()
La majorité des objets en provenance de cette abstraction sont soit des instances de Model
(un seul objet), soit des instances de QuerySet
(une collection d’objets).
Les instances de QuerySet
disposent en plus de propriétés spécifiques :
Des API de requêtes de plus haut niveau
L'ajout de méthodes à un Model
permet d'ajouter des fonctionnalités au niveau ligne d'une table de la base de données.
Augmenter un Manager
est la recommandation pour ajouter des fonctionnalités au niveau table de la base de données. Les nouvelles méthodes ne sont pas tenues de renvoyer exclusivement des instances de QuerySet
.
L'apparition de from_queryset
dans Django 1.7 facilite l'utilisation de QuerySet
personnalisés pour tirer parti de la faculté de pouvoir en chaîner des instances :
- avant Django 1.7 : Building a higher-level query API: the right way to use Django's ORM
- après Django 1.7 : Django: Custom Manager With Chainable QuerySets
Ces possibilités de personnalisation, mais aussi le fait de pouvoir avoir plusieurs Manager
et QuerySet
par Model
, permettent de bâtir des API de requêtes de plus haut niveau.
Quoi mettre où ?
C'est là qu'on entre en zone grise parce qu'il n'y a pas toujours une réponse définitive.
Le mieux est de se mettre d'accord avec son équipe sur des conventions, par exemple :
- ce qui implique une seule instance va sur la classe
Model
:@property
- ou méthode de classe si des paramètres sont nécessaires
- ce qui implique plusieurs instances va :
- sur le
Manager
- ou sur le
QuerySet
si ça peut être chaîné
- sur le
- un constructeur alternatif va dans une méthode de
Manager
(plutôt que dans une@classmethod
)
Ces conventions doivent permettre de basculer vers une architecture répandue en Django, Use fat models, and thin views, qui encapsule la complexité métier au niveau des modèles.
Le conseil le plus pragmatique pour tendre vers cette architecture vient de Tom Christie dans Django models, encapsulation and data integrity. Il préconise pour tout le code en dehors des modèles :
Never write to a model field or call
save()
directly. Always use model methods and manager methods for state changing operations.This is a simple, unambiguous convention, that's easily enforceable at the point of code review.
Cette architecture permet aussi de tester unitairement le code des modèles et de rendre les tests d'intégration des vues moins verbeux.
En résumé
Une application Django bien conçue encapsule la complexité métier grâce à l'ORM en exposant des APIs de requêtes de plus haut niveau au reste du code.
Cette approche produit une architecture fat models and thin views.
Une méthode pragmatique permettant d'y arriver :
- ne jamais écrire directement dans un champ de modèle en dehors du code des modèles
- ne jamais appeler
save()
directement en dehors du code des modèles
Si vous adoptez cette méthode à la lettre, vous devrez vous passer des ModelForm
(voir le paragraphe Breaking the contract). Vous perdrez alors certains avantages tels que la validation et la concision. C’est peut-être beaucoup demander notamment à des débutants… Une voie intermédiaire est possible avec les ModelForm
et une encapsulation un chouïa moins stricte.