Sunday 12 April 2020

ET SI LES ADAPTATEURS FAISAIENT EUX AUSSI DU DDD ?


Il y a quelques jours j’ai posté un article pour mettre en garde les gens à propos de quelques pièges qui nous pendent au nez lorsqu’on met en place une architecture hexagonale. Un de ces pièges est de faire fuir une partie de la logique du domaine vers un ou plusieurs adaptateurs (côté code d’infrastructure donc). Si je suis convaincu que c’est quelque chose qu’il faut absolument éviter pour que notre code du domaine reste cohérent et détaché de tout enjeu technique, je voudrai ici m’attarder un peu et revaloriser une zone que j’ai moi-même pris l’habitude de décrier légèrement : le code à l’extérieur de l’hexagone. Aujourd’hui, le pratiquant passionné de Domain Driven Design que je suis veut affirmer que les adaptateurs de droite de l’architecture hexagonale méritent beaucoup mieux que le traitement qu’on leur réserve habituellement. Et si ce code d’infrastructure pouvait finalement être tout aussi important que notre domaine dans certains cas, voire le siège d’enjeux majeurs pour nos systèmes ?

 

 

Les adaptateurs, ces Hérauts du DDD

Le Domain Driven Design regroupe beaucoup de choses. Mais à l’origine, il part d’une tentative d’Éric Evans de donner plus d’autonomie aux gens et aux différentes équipes qui travaillaient -parfois avec beaucoup trop de frictions- dans la même structure où il était lui aussi (dans le secteur de la finance). 

Pour y arriver, Éric eu l’idée de faire émerger sa notion clé de Contextes Délimités/Bounded Contexts. Un Bounded Context est un espace linguistique et conceptuel délimité où les mots, les gens et les modèles qui s’appliquent sont cohérents et servent à résoudre des problèmes spécifiques. Les problèmes spécifiques de gens du métier qui parlent la même langue et qui ont les mêmes préoccupations. On peut donc dire que le DDD milite pour des modèles spécifiques par usages, calqués sur des groupes de personnes.

Tout ceci est bien entendu très spécifique à une organisation et aux processus métiers mis en place dans celle-ci. Dans la même société, on pourra trouver par exemple un ou plusieurs Contextes lié au « marketing », un autre a la « comptabilité », un autre propre au « service de livraison », etc. Lorsqu’on fait du DDD, on essaie en général d’identifier et de caractériser ces différents contextes pour savoir dans lequel on se trouve, si celui-ci apporte vraiment de la valeur métier ou concerne seulement des fonctions support. On essaie également d’identifier les autres Contextes avec lesquels on va devoir collaborer. Le tout étant en général illustré dans un diagramme qui représente les Bounded Contexts comme des patates et que l’on nomme Context Map (voir l’exemple ci-dessous). 

Tout le volet stratégique du DDD décrit ces rapports entre contextes (humains et techniques) et nous fournit des patterns pour gérer telle ou telle situation. Pour connecter et faire communiquer tel ou tel contexte étant donné les rapports de force en présence. 

Voici un exemple naïf de Context Map appliqué à un groupe hôtelier qui distribue ici ses chambres d’hôtels à travers sa propre plateforme de distribution (web et apps). 

Exemple de Context Map (domaine de l’hôtellerie)


L’orienté Services FTW

Comme on lit beaucoup de choses sur le sujet, je voudrais donner quelques ordres d’idées de relations entre ce concept de Bounded Context et des artefacts logiciels plus tangibles et que vous connaissez surement mieux, histoire de rendre mes implicites un peu plus explicites. Je vais assez naturellement m’inscrire dans une architecture orientée service qui est un style d’architecture que j’affectionne particulièrement et qui est incroyablement utile quand on s’y prend bien (retirer tout ce savon de votre tête pour les lignes qui suivent ;-)

Lorsqu’on fait du DDD, la taille d’un service (au sens SOA moderne) importe peu, c’est son alignement avec le métier (et donc au sein d’un Bounded Context) qui compte. En conséquence, on trouvera souvent un ou plusieurs services (web APIs en général) au sein d’un même Bounded Context. Le pattern d’Alistair étant très pratique pour implémenter un service qui fait vraiment du métier, je l’utilise souvent pour implémenter ceux-ci. On va donc avoir en général 1 hexagone par processus (que l’on pourra scaler horizontalement). Dans certains cas on pourra mettre plusieurs hexagones au sein du même processus en les reliant entre-eux par des adaptateurs In-Proc qui ne passeront pas par le réseau pour communiquer entre-eux. C’est quelque chose qui est très pratique pour de nombreuses raisons et que l’on appelle dans le jargon DDD des Modulars Monolith. 


1 BC peut avoir n services/hexagones. On peut aussi mettre 1 ou n hexagones dans un même process (Modular Monolith)


Un ADN commun

Faire des modèles autonomes, les séparer et éviter qu’un modèle spécifique à un contexte ne vienne parasiter un autre modèle d’un autre contexte, cela ne vous rappelle pas quelque chose ?  C’est pour cela que le pattern d’Alistair convient si bien aux pratiquantes et aux pratiquants du DDD. Les ports et les adaptateurs nous permettent effectivement d’éviter que le DTO d’une APi extérieure (souvent en provenance d’un autre contexte donc) ne vienne parasiter et pénétrer dans notre hexagone qui ne doit gérer que des problèmes du contexte auquel il appartient. 

Si on ne faisait pas ça, ce couplage serait infernal car on aurait des petits bouts d’autres contextes au sein de notre code métier. Je vous laisse imaginer l’impact sur votre code au cas où les gens de l’équipe travaillant pour l’autre contexte en venaient à changer en permanence leurs contrats d’API ou DTOs sans nous demander notre avis. Pour se protéger d’une pareille situation, le DDD nous propose notamment un pattern intéressant : l’Anti-Corruption Layer (ACL).


Anti-Corruption quoi ?

L’Anti-Corruption Layer est un pattern popularisé par le DDD qui permet à un (Bounded) Context de ne pas se retrouver pollué par l’incohérence du modèle d’un autre contexte avec lequel il doit travailler (ou de ses changements trop fréquents). C’est comme un amortisseur entre 2 Contextes. On s’en sert pour réduire le couplage et notre dépendance vis à vis d’un autre Contexte. En procédant ainsi, on isole notre dépendance dans une petite boite bien protégée et a accès limité : l’ACL. On comprend donc alors que tout changement intempestif du Contexte extérieur n’aura que très peu d’impact sur notre code métier qui fait office de consommateur de l’ACL et non plus directement du code instable en provenance du Contexte extérieur.


 Pas d’ACL ici, donc le modèle métier de l’autre Bounded Context s’insère partout dans notre hexagone


Ici, notre Adaptateur/ACL empêche les modèles des autres Bounded Context de rentrer dans notre hexagone
  

Une anti-corruption layer peut-être implémentée sous différentes formes : service ou Api intermédiaire, base de données tampon (pour les vieux systèmes), ou bien juste un adaptateur (in-proc donc par rapport au code consommateur) d’une architecture hexagonale. 


Quelle Eurythmics choisir pour votre ACL ?

Tout bon article sur le DDD se doit d'avoir une section "Heuristiques" (c'est désormais obligatoire ;-) Voici la mienne :

Si vous avez plusieurs consommateurs en provenance de votre Bounded Context (potentiellement plusieurs APIs ou composant au sein de votre Bounded Context) qui ont besoin d’interagir avec cette partie de l’autre Bounded Context a l’extérieur, vous aurez tout intérêt à faire émerger un service (une API) façade qui ferait office d’ACL partagé. 

Si vous n’avez que le code d’un seul hexagone qui a besoin de ce type d’interaction avec ces systèmes tierces en provenance d’autre Bounded Contexts, cette logique d’ACL aura tout intérêt à se trouver dans un des adaptateurs de droite de votre architecture hexagonale. Il est très fréquent de commencer par implémenter un adaptateur dans un process a un endroit, et de le sortir ensuite sous forme d’api autonome si le besoin s’en fait sentir.

Une Anti-Corruption Layer peut être hébergée dans un process dédié (API externe)



Et c’est dans ce dernier cas -où un de vos adaptateurs de droite ferait office d’anti-corruption layer- que vous allez souvent retrouver des formes d’orchestrations entre plusieurs services extérieurs. Car en général aucun d’entre eux n’a été fait spécifiquement pour vous et ne réponds directement aux questions que vous vous posez dans le cadre de vos use cases et de votre domaine/contexte (celui que vous implémentez dans votre hexagone). Dans la plupart des cas, les systèmes extérieurs dont vous aurez besoin pour faire fonctionner votre hexagone seront même des systèmes legacy tous pourris, ou un mix entre un vieux système et une toute nouvelle API ne fonctionnant que sur certains cas récents. 

Votre adaptateur d’archi hexagonale va donc devoir très souvent appeler ces deux sources pour pouvoir offrir une vision consolidée a votre domaine. Du genre : appeler un premier service d’un autre BC pour récupérer des données incomplètes, traduire ou adapter une partie de sa réponse dans un autre modèle de donnée pour pouvoir ensuite forger une requête intelligible par un autre service extérieur (souvent appartenant au même BC que celui du 1 er appel), récupérer le résultat de ce second appel et adapter le tout pour produire une réponse qui conviendra enfin à votre hexagone qui aura déclenché cette requête via le port de droite que notre adaptateur ACL implémente.

 

Mon domaine n’est pas ton domaine

Dans la plupart des cas, toute cette complexité d’orchestration d’appels aux services ou bases de données tierces et d’adhérence aux modèles spécifiques de l’autre BC n’a rien à faire dans le code de Domaine de votre hexagone qui s’occupe lui d’un autre problème spécifique. Celle-ci sera donc très souvent transparente pour lui, complètement cachée dans votre adaptateur de droite. 

Exemple d’Adaptateur-ACL qui orchestre différents appels à différents back-ends/APIs d’un autre BC
  
Dans d’autres cas, vous trouverez naturel que le centre de votre hexagone aille chercher explicitement deux choses différentes via deux méthodes ou deux ports de droite différents avant de les assembler pour devenir quelque chose d’utile à votre domaine.



There is no silver bullet

Ne comptez pas sur moi pour vous donner une règle universelle et absolue pour répondre à cette question. C’est du Design, et cela va bien entendu dépendre de votre contexte et de votre domaine. La bonne question à se poser à chaque fois est : est-ce légitime et utile pour mon code du domaine de connaitre ces détails ? 

Si oui, votre hexagone doit expliciter ces interactions et ces échanges. Si non (le cas du cache misère vis à vis de l’autre BC par exemple), cette connaissance doit plutôt rester dans votre adaptateur de droite.


Une situation paradoxale

A la lumière de ces explications, vous comprendrez pourquoi je regrette vraiment d’avoir écrit dans mon précédent post qu’un « bon adaptateur est un adaptateur assez bête ». C’est le cas quand il y a une relation 1-1 (c.ad. Votre hexagone avec un autre système extérieur). Mais dans la pratique c’est même assez souvent le contraire (et encore plus si votre adaptateur est une ACL). 

Et c’est là tout le paradoxe. Depuis des années je n’ai fait que mettre l’accent sur le code du Domaine des hexagones. Avec la pratique et tous les cas non triviaux rencontrés dans ma carrière, je m’aperçois que ces adaptateurs sont beaucoup plus que de simples figurants

A ce titre, ils méritent également beaucoup mieux que d’être simplement bouchonnés dans nos tests d’acceptation. Mais sur cette partie des tests, je reviendrai très vite avec un nouvel épisode de cette série consacrée à l’architecture hexagonale.


 


No comments:

Post a Comment