DDD : Agrégats et repositories
Conception des agrégats
Nous avons vu 3 principaux éléments de la conception dirigée par le domaine.
- la notion de
ubiquitous language: faire en sorte que les concepts et le vocabulaire du domaine se retrouvent dans notre code - les agrégats : une technique pour regrouper des objets afin de garantir la consistance du domaine
- les
value objectset lesentities: deux types d'objets qui ont une notion d'identité différente
Un des sujets qu'il est facile de mal comprendre est la conception des agrégats et leur relation avec les repositories.
Exercice : retour sur les concepts évoqués jusqu'à maintenant
Si nous retournons à l'atelier sur la gestion des patients, nous avons
- une tête d'agrégat avec la classe
PatientAssurable PatientAssurablecontient unHistoriquePatientHistoriquePatientcontient une liste deSejourHorsQuebec
Quelle notion du DDD a mené au nommage de ces classes?
☑️ Solution
Il s'agit du ubiquitous language. Les notions PatientAssurable, HistoriquePatient et SejourHorsQuebec font partie du domaine et sont utilisées par les personnes de la RAMQ qui traitent les dossiers d'assurabilité. C'est pourquoi on les retrouve dans notre code.
Parmi ces 3 classes, lesquelles seraient des value object?
PatientAssurableHistoriquePatientSejourHorsQuebec
☑️ Solution
Réponse B et C :
- Un historique est purement défini par ses attributs (date de naissance, date d'inscription, date de décès)
- Un séjour est purement défini par ses attributs (date de départ, durée et motif)
À l'inverse, un PatientAssurable peut évoluer dans le temps tout en possédant une identité propre.
De ce fait, quels problèmes voyez-vous dans la conception des objets?
☑️ Solution
PatientAssurableest une entité et devrait donc pouvoir être identifiée sans se baser sur ses attributs. Il lui faudrait un champ d'identification uniqueHistoriquePatientest unvalue objectet devrait donc être immuable. Les méthodesajouterSejour,ajouterDeces,ajouterInscriptionRAMQne devraient pas changer l'instance à laquelle elles sont attachées. Il faudrait plutôt recréer une nouvelle instance lorsqu'on veut un nouvel historique.
Le rôle principal des agrégats est de garantir le respect des règles du domaine et notamment des invariants. Pouvez-vous identifier les différents invariants garantis par l'agrégat PatientAssurable ?
☑️ Solution
- Il est invalide d'avoir une date de décès avant la date de naissance
- Il est invalide d'avoir un séjour avant sa naissance ou après son décès
- Il est invalide d'avoir 2 séjours différents avec des dates qui se chevauchent
- Il est invalide de s'inscrire avant sa naissance ou après son décès
- Un patient n'est pas assuré avant sa naissance ou après son décès
- Un patient doit être inscrit pour être assuré
- Un patient qui quitte le Québec pour plus de 183 jours n'est plus assuré sauf s'il quitte pour travailler ou étudier
Exercice : agrégats et repository
L'Université Laval se munit d'un forum de discussion similaire à Reddit. Ce forum est organisé en sous-forums (annonces générales, vie étudiante, génie informatique, médecine, etc.). Les utilisateurs peuvent poster des messages dans les sous-forums, "upvoter" ces messages et/ou y répondre, ils peuvent également s'abonner à des sous-forums pour avoir les nouvelles dans leur fil d'actualité.
Nous allons modéliser ce domaine avec 2 agrégats :
- les utilisateurs
- le forum, qui contient les sous-forums, les messages et les réponses
Veuillez cloner le repository github
Agrégat Utilisateur
Commençons par étudier le code se rapportant aux utilisateurs
- Dans le package
domaine.utilisateursnous avons un agrégat.Utilisateur(identifié par son courriel) est la tête d'agrégat.Courrielest unvalue object. - Dans le package
persistence.accesdonneesnous avonsUtilisateurJSONqui représente la structure de données d'un utilisateur au format JSON etUtilisateurJSONDAOpour lire et écrire des utilisateurs dans un fichier.
Votre premier exercice consiste à créer une interface UtilisateurRepository ainsi qu'une implémentation de cette interface qui utilisera la DAO et les structures de données pour lire et écrire dans un fichier.
Pour cela, il faut faire compiler puis passer les tests dans la classe _exercice1.UtilisateurRepositoryJSONTest.
Conseils :
- Assurez-vous de créer les bonnes classes et interfaces aux bons endroits.
- Faites passer les tests un à la fois
- Une fois tous les tests unitaires au vert, vous pouvez vérifier vos résultats avec un scénario d'ensemble dans
_exercice1.UtilisateurRepositoryJSONVerificationComplete - Ne modifiez pas le code fourni, vous devez seulement créer du nouveau code qui utilise l'existant.
- Ne vous préoccupez pas trop de la gestion d'erreurs et de fonctionnalités avancées. Le but est de bien comprendre le lien entre agrégats et repository ainsi que de placer les interfaces et implémentations aux bons endroits. Nous n'essayons pas de faire un repository parfait.
☑️ Solution
Liste de vérification
- Est-ce que votre interface
UtilisateurRepositoryse trouve bien dans le packagedomain(normalement dansdomain.utilisateurs)? - Est-ce que votre implémentation
UtilisateurRepositoryJSONse trouve bien dans le packagepersistence? Est-ce que leUtilisateurJSONDAOest utilisé par leUtilisateurRepositoryJSONpour ses opérations? - N'y a-t-il bien aucune dépendance de
domainverspersistence? - Tous les tests de
_exercice1sont au vert?
Agrégat Forum
Passons maintenant à l'agrégat Forum qui contient des Messages organisés en SousForums et qui peuvent avoir des Reponses associées.
De la même façon que pour Utilisateur, vous avez du code de DAO et des structures de données JSON qui sont fournies.
Avant de vous lancer dans la création des repository, étudiez bien le code du domaine. Une erreur s'est glissée dans la conception des agrégats, pouvez-vous la repérer?
☑️ Solution
Comparez le champ auteur de Message et de Reponse.
Pour l'un, nous avons un value-object Courriel, l'autre référence l'agrégat Utilisateur directement. Il est très rare et peu recommandé qu'un agrégat référence un autre agrégat. Message et Reponse doivent utiliser Courriel tous les deux.
Procédez à la correction nécessaire. Le compilateur vous guidera vers d'autres erreurs à corriger ce qui ne devrait pas vous prendre trop de temps.
Une fois cette correction faite, vous pouvez commencer à implémenter l'interface ForumRepository en créant un Repository capable d'interagir avec les DAO JSON fournies. Les tests dans _exercice2.ForumRepositoryJSONTest sont là pour vous guider.
Encore une fois, il n'est pas question d'implémenter un Repository parfait avec de nombreuses fonctionnalités et une gestion d'erreur en béton. Concentrez-vous sur
- Comprendre qu'un
Repositoryest responsable de la persistance d'un agrégat correspondant (dans notre cas, simple lecture et écriture) - Faire passer les tests, une étape à la fois en écrivant du code propre. Souvenez-vous des ateliers de réusinage : les principes OO pourront vous aider à maintenir votre code lisible et maintenable.
- Respecter le sens des dépendances (l'interface vs l'implémentation du repository, dans quelle couche etc.)
☑️ Solution
Liste de vérification
- Est-ce que votre interface
ForumRepositoryse trouve bien dans le packagedomain(normalement dansdomain.messages)? - Est-ce que votre implémentation
ForumRepositoryJSONse trouve bien dans le packagepersistence? Est-ce qu'elle utilise les DAO pour lesSousForum, lesMessageet lesReponse? - N'y a-t-il bien aucune dépendance de
domainverspersistence? - Tous les tests de
_exercice2.ForumRepositoryJSONTestsont au vert?
Pour aller plus loin
Ce document de référence de Vaughn Vernon offre une bonne vue d'ensemble sur la conception des agrégats. Il est fortement recommandé de le lire.