GLO-4002 - Site du cours 2023

DDD : Aggrégats et repositories

Conception des aggrégats

Nous avons vu 3 principaux éléments de la conception dirigée par le domaine.

  1. la notion de ubiquitous language : faire en sorte que les concepts et le vocabulaire du domaine se retrouvent dans notre code
  2. les aggrégats : une technique pour regrouper des objets afin de garantir la consistence du domaine
  3. les value objects et les entities : 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 un HistoriquePatient
  • HistoriquePatient contient une liste de SejourHorsQuebec

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?

  1. PatientAssurable
  2. HistoriquePatient
  3. 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 unique
  • HistoriquePatient est un value object et devrait donc être immuable. Les méthodes ajouterSejour, 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 un value object.
  • Dans le package persistence.accesdonnees nous avons UtilisateurJSON qui représente la structure de données d'un utilisateur au format JSON et UtilisationJSONDAO 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 package domain? (normalement dans domain.utilisateurs)?
  • Est-ce que votre implémentation UtilisateurRepositoryJSON se trouve bien dans le package persistence? Est-ce que le UtilisateurJSONDAO est utilisé par le UtilisateurRepositoryJSON pour ses opérations?
  • N'y a-t-il bien aucune dépendance de domain vers persistence?
  • Tous les tests de _exercice1 sont au vert?

Aggrégat Forum

Passons maintenant à l'aggrégat Forum qui contient des Messages organisés en SousForums et qui peuvent avoir des Reponses 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 package domain? (normalement dans domain.messages)?
  • Est-ce que votre implémentation ForumRepositoryJSON se trouve bien dans le package persistence? Est-ce qu'elle utilise les DAO pour les SousForum, les Message et les Reponse?
  • N'y a-t-il bien aucune dépendance de domain vers persistence?
  • 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.