ValueObject : qu'est ce que c'est ?

Quelle différence entre ValueObject et Entity ?

Souvent associés au Domain Driven Design (DDD), les ValueObjects font partie des concepts méconnus mais pourtant très simples que chaque développeur devrait connaître.

Le DDD divise nos objets métiers de base en deux catégories : les entités (entity) et les valeurs (value object). Avant même de connaître la définition de ces concepts, il faut retenir une chose essentielle : cette division dépend de votre coeur de métier, une valeur pour moi sera peut-être une entité pour vous, nous reviendrons là-dessus par la suite.

Les entités et les valeurs sont des objets au sens POO du terme, ce sont des instances d'une classe. Ils sont tous une représentation d'une partie de notre métier (le domain). La principale différence entre entités et valeurs réside dans la notion "d'identité" : certains objets, les entités, ont une identité propre et importante dans le système d'information (SI), les autres sont des valeurs.

Entity = identité unique + contenu : l'identité sert à différencier les objets. ValueObject = contenu : l'analyse du contenu nous permet de manipuler l'objet.

Dans une base de données relationnelle type MySQL, cela se traduirait comme ceci : Table personne : n°sécurité sociale | prénom | nom etc... : c'est une entité identifiable grâce au n° de sécurité sociale. Table localisation : longitude | latitude : c'est une valeur, l'ensemble des champs forment la valeur. Deux lignes de cette table avec la même longitude et la même latitude seraient égales.

Prenons des exemples simples qui [ sont | devraient être ] des value objects dans 95% des cas : une somme d'argent, une adresse, une URL, une requête HTTP etc... Ce sont des types d'objets qui, soit n'ont pas d'identité propre, soit leur identité n'est d'aucune utilité et nous choisissons de ne pas la prendre en compte. Pour la somme d'argent, cette notion paraît assez évidente : peu importe la composition de 10 euros, que ce soit un billet, des pièces ou un chèque, ça reste 10 euros.

Exemples de ValueObject : des montants

$two_euros = 2; $five_euros = 5; $amount1 = new amount($two_euros * 5, 'EUR'); $amount2 = new amount($five_euros * 2, 'EUR'); /* 10 euros is equal to 10 euros */ $should_be_true = $amount1.equals($amount2);

Si l'on modifie un seul des attributs des objets montants (la valeur ou la devise), ce ne sera plus le même montant.

Exemples de ValueObject : des montants

$amount1 = new amount(10, 'EUR'); $amount2 = new amount(10, 'USD'); /* 10 euros is not equal to 10 dollars */ $should_be_false = $amount1.equals($amount2);

Les ValueObjects sont donc des objets immutables !

A partir de ce constat, nous allons mieux comprendre pourquoi une requête HTTP doit être considérée comme une valeur. Tout d'abord, une requête HTTP doit être immutable : si nous en modifions le moindre caractère ce n'est plus la même requête et le comportement engendré sur le serveur pourrait être radicalement différent. Vous pouvez trouver un exemple dans la documentation de PSR-7 - HTTP message interfaces. Plus important, l'identité d'une requête HTTP nous importe peu, seul son contenu est utile. Pour preuve, le contenu d'une requête provoque la génération d'une réponse qui est souvent mise dans un système de cache. Une autre requête avec le même contenu se verra retourner la même réponse.

Maintenant complexifions les choses et prenons l'exemple d'une entreprise qui propose un système d'abonnement. Cette entreprise est divisée en deux parties avec des SI bien distincts : le back qui gère le catalogue d'offres disponibles pour les abonnements, le front qui affiche ces offres au client. Les deux services dialoguent via un système de web-services et ne partagent absolument aucune portion de code.

Le front, via l'un de ces web-services, demande au back de lui retourner la liste des offres auxquelles est éligible le client en cours de navigation sur le site.

Requête: identifiant client + informations de contexte (pays, marque du téléphone etc...).

Le back dispose d'un catalogue d'offres

ID (unique) | Nom (pas unique) | Prix | Filtres d'éligibilité par rapport au contexte -------------------------------------------------------------------------------------------- 1 | Offre basique | 10€/mois | iPhone en France -------------------------------------------------------------------------------------------- 2 | Offre basique | 5€/mois | Nouveau client sur iPhone en France -------------------------------------------------------------------------------------------- 3 | Offre supérieure | 15€/mois | iPhone en France -------------------------------------------------------------------------------------------- 4 | Offre supérieure | 12€/mois | Android en France

Côté back nous avons donc des offres, sur lesquelles des clients ont déjà souscrit, qui peuvent changer de prix ou de filtres d'éligibilité. Une réponse, contenant toutes les offres, est fournie au front :

Réponse du back vers le front

[ { "nom": "Offre Trop Cool !", "prix": "10€/mois" }, { "nom": "Offre Trop Cool !", "prix": "5€/mois" }, { "nom": "Offre Encore + Trop Cool !", "prix": "15€/mois" } ]

Le front, afin de rester dans son scope de responsabilité, ne veut connaitre qu'un nom et un prix. Il se permet seulement de comparer et de trier les offres afin de vérifier qu'il n'affichera pas plusieurs fois le même nom d'offre. Plusieurs offres différentes d'un point de vue back peuvent ainsi être vues de la même façon par le front et donc correspondre à une seule offre : le couple nom / prix détermine la valeur de cette offre.

Résumons la situation. Pour représenter le concept de l'offre :

  • Le back dispose d'un objet complet, évolutif dans le temps, fortement lié à son coeur de métier. Bien que certaines puissent être comparées et se ressembler, deux offres ne peuvent jamais être égales. Elles ont chacune leur identité. L'objet représentant une offre en back est une Entity !
  • Le front dispose d'un objet qui ne contient que les informations dont il a besoin pour sa partie de métier. Il n'a pas le droit de modifier les informations contenues dans cet objet, il peut seulement les comparer. De son point de vue, deux offres peuvent être égales, il n'a pas besoin de connaître leur identité. L'objet représentant une offre en front est une ValueObject !

Si vous souhaitez aller plus loin, vous pouvez lire les articles sur les ValueObject par Martin Fowler.