Domain Driven Design : Comment Organiser ses calculs ?

Cet article trouve sa place dans ma série d’articles sur le Domain Driven Design:

Domain Driven Design : Partie 1

Domain Driven Design : Partie 2

Récemment j’ai eu à travailler sur un projet dans le domaine de l’agriculture, où un des besoins était de fournir une interface pour saisir des données scientifiques et retourner des calculs.

Plusieurs interrogations se sont posées vu leur nombre : comment les organiser et comment les tester ?

En effet comment savoir que tel résultat est juste si on a aucune idée de ce que l’on doit obtenir ? Et comment faire en sorte de garder une architecture simple et cohérente.

Demander de l’aide à l’expert du domaine

Ce que j’ai fais en premier lieu, c’est demander à “l’expert” du domaine chez le client, de nous remplir un fichier yaml sous un format précis.

Un expert en DDD, est la personne identifiée chez le client comme étant la personne ayant la meilleure connaissance du domaine. À ne pas confondre avec l’expert du domaine chez vous.

C’est important d’avoir le bon interlocuteur pour obtenir les bonnes réponses à vos problématiques. L’erreur parfois c’est d’avoir des intermédiaires qui risquent de générer du bruit et de la perte d’information en voulant la restituer. Dans notre cas la personne était un ingénieur, nous avons pu travailler avec lui directement et de manière efficace.

J’ai donc demandé à l’expert de me donner le nom des calculs, les valeurs en entrées et le résultat attendu sous un format précis :

calculs:
  calcul_A:
    given:
      param_1: 2.2
      param_2: 0.01
      param_3: 32
    expected: 8

Ainsi on a pu tester automatiquement énormément de calculs, et s’il nous soumettait un nouveau fichier yaml, on pouvait le valider rapidement.

Au début le format YAML lui était inconnu, mais il a compris l’intérêt. Eric Evans (auteur du Blue Book et du DDD), expliquait qu’il ne fallait pas craindre d’impliquer le client et lui montrer le code qu’on écrit. Normalement le client devrait être en mesure de comprendre notre code si on nomme les choses conformément au langage commun adopté sur le projet.

Un contexte séparé pour les calculs ?

Je me suis ensuite demandé comment organiser et architecturer le code pour tous ses calculs.

Mon architecture est basée sur le modèle hexagonal, et sur une philosophie DDD. J’ai un AgricultureContext (j’invente un nom pour l’article), et je ne voulais pas que les calculs fassent partie de ce contexte.

Dans ce nouveau contexte, on retrouverait des éléments commun en terme de nommage (en DDD on parle tous le même Ubiquitous Languages, selon les contextes on en fait pas la même chose), son but ne serait que dans la connaissance des calculs et comment les restituer. On pourrait dire qu’il s’agit d’un SubDomain.

Pourquoi ? et bien les calculs, c’est toute une connaissance à part entière à gérer, qui peut être indépendante du reste du projet. La seule contrainte à respecter, c’est le “contrat” entre les deux Contextes. C’est comme une API externe, il y a un format précis pour la requête et pour la réponse.

Donc en résumé DDD, notre Core Domain (AgricultureContext) va devoir s’intégrer avec d’autres Bounded Contexts (CalculContext). En DDD on appelle cette intégration Context Mapping.

Responsabilité unique ou single responsibility

Je suis un fervent défenseur de la responsabilité unique. Une classe ne doit faire qu’une seule chose et bien. On peut étendre ce concept à un niveau plus général.

Ainsi on peut faire évoluer le code dans le CalculContext sans aucun impact sur le reste. On pourrait même un jour faire un autre projet séparé, reprenant le code du CalculContext, et pourquoi pas à la place de partager une interface PHP dans un monoprojet, de passer par une API externe.

En tout cas c’est la voie que j’ai choisi. Avec le recul, est-ce que j’aurai pu mettre tous les calculs dans le même contexte ? J’y ai pensé, mais à faire ça, je sais que j’aurai eu un code très couplé et moins simple à tester.

Petit point DDD

Ici le CalculContext domine l’AgricultureContext, en effet c’est ce dernier qui a besoin des données de l’autre. On est dans une relation Customer-Supplier (Client-Fournisseur). Il revient au client de s’adapter au fournisseur.

Pour faire le lien entre les deux contextes, j’autorise un service à être appelé dans le contexte Agriculture. Pour cela j’utilise le concept d’anti-corruption layer. C’est à dire transformer le model d’un domaine vers un autre. Par exemple traduire une classe vers une autre, du json vers une classe, ou une classe vers du xml etc. Le but étant de ne pas manipuler les données d’un autre domaine sans l’avoir transformé pour son propre domaine auparavant.

source : https://docs.microsoft.com/fr-fr/azure/architecture/patterns/anti-corruption-layer

Si je n’avais pas fais ça, je me serai retrouvé à manipuler des classes d’un autre domaine (un autre namespace), ce qui viendrait à coupler notre code très fortement et casser les barrières de nos contextes. Ne faites jamais cela.

Si vous vous retrouvez malgré vous à devoir manipuler des classes d’un autre Contexte, demandez-vous si ce Contexte a vraiment du sens finalement. Revoyez votre architecture.

Les calculs

Concernant les calculs en eux mêmes, j’ai choisi de créer une classe par calcul, avec en paramètres un tableau de données, avec validation des noms et du type attendu.

J’utilise l’OptionsResolver en interne pour m’aider à valider le nom des paramètres et leur types (string, float, int et etc.). C’est assez pratique pour éviter de se tromper dans les noms des paramètres.

En fin de compte le fichier Yaml respecte des noms de calculs et des noms de paramètres repris dans l’OptionsResolver, permettant ainsi de relativement automatiser l’exécution des calculs !

Fin

On arrive à la fin de cet article, j’espère en faire d’autres en arrivant à piocher quelques exemples de mon travail !

Voir l’étude de cas
Lire l’article
Voir le témoignage
Fermer