DDD : Aggrégats et repositories
Conception des aggré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 aggrégats : une technique pour regrouper des objets afin de garantir la consistence du domaine
- les
value objects
et 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 aggré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'aggrégat avec la classe
PatientAssurable
PatientAssurable
contient unHistoriquePatient
HistoriquePatient
contient une liste deSejourHorsQuebec
Quelle notion du DDD a mené au nommage de ces classes?
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 retours dans notre code.
Parmi ces 3 classes, lesquelles seraient des value object
?
PatientAssurable
HistoriquePatient
SejourHorsQuebec
Réponse B et C :
- Un historique est purement défini par ses attributs (date de naissance, date d'inscription, date de decè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?
PatientAssurable
est une entité et devrait donc pouvoir être identifiée sans se baser sur ses attributs. Il lui faudrait un champ d'identification uniqueHistoriquePatient
est unvalue object
et devrait donc être immuable. Les méthodesajouterSejour
,ajouterDeces
,ajouterInscriptionRAMQ
ne 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 aggré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'aggrégat PatientAssurable
- 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 : aggré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 aggrégats :
- les utilisateurs
- le forum, qui contient les sous-forum, les messages et les réponses
Veuillez cloner le repository github
Aggrégat Utilisateur
Commençons par étudier le code se rapportant aux utilisateurs
- Dans le package
domaine.utilisateurs
nous avons un aggrégat.Utilisateur
(identifée par son courriel) est la tête d'aggrégat.Courriel
est unvalue object
. - Dans le package
persistence.accesdonnees
nous avonsUtilisateurJSON
qui représente la structure de données d'un utilisateur au format JSON etUtilisationJSONDAO
pour 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 verifier vos résultats avec un scenario 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 aggrégats et repository ainsi que de placer les interfaces et implémentations aux bons endroits. Nous n'essayons pas de faire un repository parfait.
Liste de vérification
- Est-ce que votre interface
UtilisateurRepository
se trouve bien dans le packagedomain
? (normalement dansdomain.utilisateurs
)? - Est-ce que votre implémentation
UtilisateurRepositoryJSON
se trouve bien dans le packagepersistence
? Est-ce que leUtilisateurJSONDAO
est utilisé par leUtilisateurRepositoryJSON
pour ses opérations? - N'y a-t-il bien aucune dépendance de
domain
verspersistence
? - Tous les tests de
_exercice1
sont au vert?
Aggrégat Forum
Passons maintenant à l'aggrégat Forum
qui contient des Message
s organisés en SousForum
s et qui peuvent avoir des Reponse
s associées.
De la même facon 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 aggrégats, pouvez-vous la repérer?
Comparez le champ auteur de Message
et de Reponse
.
Pour l'un, nous avons un value-object Courriel
, l'autre référence l'aggrégat Utilisateur
directement. Il est très rare et peu recommandé qu'un aggrégat référence un autre aggré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 la 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
Repository
est responsable de la persistence d'un aggré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 et des principes OO pourra vous aider à maintenir votre code lisible et maintenable.
- Respecter le sens des dépendances (l'interface vs l'implémentation du repository, dans quel couche etc.)
Liste de vérification
- Est-ce que votre interface
ForumRepository
se trouve bien dans le packagedomain
? (normalement dansdomain.messages
)? - Est-ce que votre implémentation
ForumRepositoryJSON
se trouve bien dans le packagepersistence
? Est-ce qu'elle utilise les DAO pour lesSousForum
, lesMessage
et lesReponse
? - N'y a-t-il bien aucune dépendance de
domain
verspersistence
? - Tous les tests de
_exercice2.ForumRepositoryJSONTest
sont au vert?
Pour aller plus loin
Ce document de référence de Vaugh Vernon offre une bonne vue d'ensemble sur la conception des aggrégats : Il est fortement recommandé de le lire.