julienleicher

• 6 minutes de lecture

Le Domain Driven Design, première approche

Depuis quelques temps, on me confie des missions de Lead technique au sein de mon entreprise et étant passionné d’architecture logicielle, j’ai décidé de m’essayer au DDD. Après avoir essuyé quelques plâtres, je me suis dis que mon expérience pourrait aider de jeunes développeurs à y voir plus clair sur cet acronyme car même si on trouve énormément d’ouvrages traitant du sujet, mettre le pied à l’étrier peut s’avérer déroutant.

Aussi, avant de se faire plaisir avec des algos de derrière les fagots, il va falloir se poser, écouter les experts du domaine, appréhender leur métier et apprendre à le modéliser correctement.

Avant toute chose, pour ceux du fond qui ne le savent pas encore, le Domain Driven Design, ou DDD, est une approche de la conception logicielle qui consiste à se focaliser en premier lieu sur le domaine métier que tente de capturer votre application plutôt que sur des considérations techniques. Le DDD, c’est avant tout se préoccuper du quoi, plutôt que du comment.

Alors c’est bien gentil, mais concrètement, je suis développeur, on me donne un nouveau projet et je décide d’utiliser les principes mis en avant par le DDD, par où je commence ? Par modéliser le domaine !

Un dépôt Github vous permettra de suivre ce début d’aventure palpitante !

Identifier les contextes

Dans n’importe quelle application, la première étape sera d’identifier les différents contextes exprimés. Pour identifier ces contextes, rien de tel que la discussion. Discuter avec les experts métiers et utiliser leur langage plutôt que le vôtre vous permettra de créer ce qu’on appelle le langage omniprésent (ubiquitous language en anglais). En utilisant le même vocabulaire dans vos échanges ET dans votre code, vous faciliterez la compréhension à tous les niveaux.

Un terme particulier n’aura, en règle générale, de sens qu’au sein d’un contexte défini. Se focaliser sur un contexte permet d’éliminer le bruit et de se concentrer sur l’essentiel.

Je vous propose de prendre, tout au long de cet article, l’exemple d’un blog très simple. Pour ce blog, imaginons qu’on ressorte les différentes catégories d’objets suivantes (je choisi volontairement un exemple très simple) :

  • Utilisateurs
  • Articles
  • Catégories

Ici, on aurait tendance à penser que toutes ces ressources appartiennent au même contexte. Mais quand on y réfléchit, est-ce que l’utilisateur et la gestion des accès associée fait partie de l’essence du blog ? Quand je parle de blog, je parle avant tout de catégories qui contiennent des articles !

Aussi, je ressors 2 contextes : Le contexte de gestion des accès, et le contexte de blogging. Le premier se focalisant sur la création des utilisateurs, des permissions et le second sur le plus important dans notre cas, la gestion du blog.

2 contextes, donc 2 librairies indépendantes. Un article possèdera bien évidemment une référence vers un utilisateur, mais il s’agira d’un simple identifiant, on y reviendra.

Créer les entités

Une entité, c’est une classe qui possède une notion d’unicité assurée par son identité . Elle possède une durée de vie longue et son état peut changer du tout au tout. Prenons un article, il possède un titre, un contenu et il doit nous être possible de le distinguer des autres articles. Son titre aurait pu faire l’affaire en tant qu’identité, si celui-ci n’était pas modifiable. Ce n’est pas le cas ici, donc on ajoutera un numéro unique, attribué à la création de l’entité. Il en est de même pour les catégories. Une entité à tout à fait le droit de référencer une autre entité par son identité. Ainsi, dans notre classe Article, on aura une propriété CategoryId et une autre AuthorId référençant respectivement une catégorie et un utilisateur.

Pour faire un gros raccourci qui me vaudra des coups de bâtons, disons qu’une entité, c’est une table dans votre base de données et que son identité, c’est tout simplement sa clé primaire.

Une entité est responsable de son intégrité, et il est souhaitable qu’elle ne rentre pas dans un état “non prévu”. Pour forcer cela, si le langage que vous utilisez vous le permet, mettez tous les mutateurs en visibilité privée et créez des méthodes publiques véhiculant un sens métier plus fort. Pour le titre de l’article par exemple, on masquera le mutateur setTitle en le remplaçant par une méthode void Rename(string newTitle).

Toujours pour éviter un état incorrect, passez les propriétés obligatoires dans le constructeur, ainsi, l’utilisateur de votre librairie ne pourra pas instancier d’objet non valides. Dans l’exemple de l’article, on décide que l’auteur et le titre doivent forcément être renseignés, dans ce cas le constructeur prendra en paramètres l’identité de l’auteur ainsi qu’un titre.

Vous ne souhaitez pas que l’auteur soit modifiable après la création initiale de l’article ? Simple, n’exposez pas de méthode permettant la modification !

La beauté du DDD est là. Votre code se suffit à lui-même, il exprime directement les besoins identifiés et force des règles de manière simple.

Persister nos entités

Il est évident que nous avons besoin de persister nos objets pour obtenir une application un temps soit peu intéressante. Cette notion de persistance a besoin d’être exprimée dans notre domaine, sans pour autant venir se mettre au travers de notre modélisation avec une implémentation particulière liée à une base de données. Le DDD met en avant l’architecture hexagonale à base de ports et d’adaptateurs.

Ça semble complexe mais c’est en fait enfantin. Quand vous identifiez une action devant être portée par votre domaine et dont l’implémentation sera à la charge de l’environnement client, créez une interface spécifique et définissez le contrat devant être rempli, l’interface représentera alors le port, et l’implémentation fournie à l’exécution l’adaptateur.

C’est le cas pour l’accès à la persistance, on créera ce qu’on appelle des dépôts ou repositories contenant les différentes opérations nécessaires pour la persistance. Pour gérer les articles, on créera une interface IArticleRepository exposant les méthodes void Add(Article entity), void Delete(Article entity), Article[] GetAll() et tout autre méthode s’avérant nécessaire.

Pour nos utilisateurs, on aura besoin de hasher le mot de passe, une autre interface verra alors le jour : IPasswordHasher. Le but ici, encore une fois, c’est d’exprimer dans notre domaine que nos mots de passe devront être hasher mais sans se soucier une seule seconde du comment, on parlera alors de services du domaine.

En Domain Driven, tout est une question de responsabilités. Chaque partie de notre architecture existe dans un but précis. Les dépôts se chargent uniquement de la persistance de nos entités et doivent les recréer dans leur globalité. Les services du domaine permettent d’ouvrir notre architecture pour des opérations précises et des responsabilités identifiées.

Alors, ça s’est bien passé ?

Bon ok, nous n’avons fait qu’effleurer le sujet et je suis conscient que c’est loin d’être parfait pour le moment mais j’espère avoir été assez clair pour cette première approche. Si vous avez des questions ou des remarques, n’hésitez pas à les partager ! On se retrouve très bientôt pour poursuivre cette aventure !