GLO-4002 - Site du cours 2023

DDD : Les objets valeurs

Vue d'ensemble

Dans un domaine riche, différents objets ont une notion d'identité différente.

  • Pour certains, l'identité n'est pas critique essentielle et peut être basée simplement sur la valeur des champs de l'objet : il s'agit de valeurs (value objects en anglais). L'exemple typique d'une valeur est un objet Couleur. Dans la plupart des contextes, 2 couleurs ayant les mêmes valeurs RBGA peuvent être considérées identiques et n'ont pas besoin d'être distinguées.

  • Pour d'autres, l'identité doit être gérée de manière plus précise et on recourt souvent à un champ d'identification. Ce sont des entités (entities en anglais), également appelées références. C'est souvent le cas d'une personne. En effet, 2 personnes peuvent avoir le même nom et prénom, la même date de naissance etc. Plutôt que de se baser sur une logique compliquée par rapport à différents champs, on préfère avoir un champ qui sert d'identifiant afin d'éviter les quiproquos.

Deux exemples bancaires

Dans les exemples ci-dessous, les objets sont-il des value object ou une `entity``

var dixDollars = new Montant(10, Monnaie.CAD);

Une instance de la classe Montant est identique à une autre si elle ont la même quantité (10) et la même monnaie (CAD). Il s'agit d'un value object

var numeroCompte = 11856002;
var soldeCourant = new Montant (4200, Monnaie.CAD);
var compteCheque = new CompteCheque(numeroCompte, soldeCourant);

Le soldeCourant est une instance de Montant donc il s'agit toujours d'un value object. Le CompteCheque est un entity car son identité ne dépend pas du solde (le même compte voit son solde changer à travers le temps mais il s'agit toujours du même compte) mais bien du numéro de compte.

Il est essentiel de retenir cette différence :

  • Les entities peuvent évoluer dans le temps et conserve une identité propre. Elles sont donc mutables (leurs attributs peuvent changer tout en conservent la même identité)
  • les value objects sont purement définies par leur attributs et n'ont pas d'identité propre. Elles sont donc immuables (on ne change pas leurs attributs, on créé simplement un objet avec les attributes requis à la demande)

Retour dans la gestion des cliniques et des patients

Dans les exercices précédents, nous avons développé un système de gestion des patients assurables ou non.

Parmi les objets suivants, lesquels devraient être des value objets et lesquels sont des references

  1. PatientAssurable
  2. SejourHorsQuebec
  3. HistoriquePatient

Un PatientAssurable devrait être une reference car on ne peut pas se baser simplement sur le nom pour distinguer 2 personnes. Des instances de SejourHorsQuebec ou de HistoriquePatient sont par contre des value objects. Deux objets de type SejourHorsQuebec ayant les mêmes valeurs peuvent être condirés identiques dans ce contexte.

Si on ajoute un numéro RAMQ au PatientAssurable, cela nous donnerait un bon moyen d'identification. Vrai ou faux?

Faux, bien qu'une personne couverte par la RAMQ est identifiée de manière unique par son numéro RAMQ, on ne peut supposer que toutes les personnes qui vont se présenter à la clinique auront un numéro RAMQ (exemple : un immigrant étranger non inscrit).

Exercice

Nous allons implementer un gestionnaire de contacts simpliste en utilisant des entites pour les contacts et des value objects pour leurs informations (nom, date de naissance, courriel).

Ouvrez le code de ce repository github et implémentez les éléments suivants dans l'ordre. Les tests unitaires associés doivent passer pour considérer le code valide.

Courriel

La classe Courriel doit être une value object immuable qui a la capacité de valider l'adresse courriel qui lui est donnée. Vous pouvez utiliser l'expression régulière sur https://owasp.org/www-community/OWASP_Validation_Regex_Repository pour vous faciliter la vie.

Quelle principe que vous avez-vu récemment justifie la création d'une classe séparée pour représenter le courriel?

Le SRP, pour s'assurer que la classe Courriel gère les courriels sans polluer la classe Contact.

Contact

La classe Contact doit être une entity qui conserve son identité via un UUID. Un Contact a un nom, une date de naissance, une addresse courriel qui peuvent tous être changés via Contact. Cependant, ces champs ne doivent pas être exposés en dehors de la classe Contact.

En terme de DDD, que forment Contact et Courriel?

Ils forment un aggrégat, Contact est la tête d'aggrégat.

Detecteur de Doublons

La classe Contact est une entité donc 2 contacts avec les mêmes informations mais un ID différent ne sont pas égaux. Implémentez la possibilité de détecter ce genre de doublons dans la classe DetecteurDoublons

Afin de respecter le concept d'aggrégat, les informations d'un contact ne doivent pas fuiter hors de l'aggrégat! Laissez-vous guider par les tests.

Carnet de contacts

Terminons l'exercice en implémentant CarnetContacts qui gère un ensemble de Contact. Ce carnet de contacts doit permettre l'ajout de contacts mais offre aussi la possibilité de détecter les contacts similaires.

Il permet également de supprimer. Un contact qui est supprimé doit être conservé mais son statut doit changer en conséquence. Encore une fois, laissez-vous guider par les tests.

Pourquoi conserver des contacts avec un statut archivé au lieu de simplement les supprimer?

La fonctionnalité de voir les contacts archivés peut être intéressante pour nos utilisateurs.

De plus, Contact étant une entity avec une identité qui se maintient dans le temps (et n'est pas seulement basée sur la valeur de ses champs), il est pertinent d'être prudent dans la gestion de son cycle de vie.

Immuabilité

Un objet est dit immuable (immutable en anglais) si il est impossible de changer ses propriétés une fois qu'il a été instancié.

public class Point {
  private final int x;
  private final int y;
  private final int z;

  public Point(int x, int y, int z) {
    this.x = y;
    this.y = y;
    this.z = z;
  }
  //pas de setters

  public Distance distance(Point autrePoint) {
    //maths
  }
}

Il est recommandé d'avoir des value objects immuables. Pourquoi?

Imaginez que ce point immuable est utilisé dans un système de traffic aérien.

Si on utilise des objets Point pour représenter la position d'un avion, il serait très dangereux qu'un acteur change un objet Point après que celui ait été assigné à un Avion pour représenter sa position... Tous les calculs de distances seraient faussés, la sécurité des avions et de leurs passagers seraient compromise

var position1 = new Point(100, 100, 2000);
var avion1 = new Avion(1, position1);

var position2 = new Point(110, 120, 1980);
var avion2 = new Avion(2, position2);
// ces 2 avions sont trop proches et le système devrait avertir les contrôleurs du traffic

position1.setY(300);
// la position du premier avion a été artificiellement changée alors que les avions sont toujours trop proches!