Makina Blog

Le blog Makina-corpus

Itéra­tions vers le DDD et la clean archi­tec­ture avec Symfony (2/2)


Quels virages avons-nous pris après un premier projet expé­ri­men­tal pour stabi­li­ser notre concep­tion logi­cielle, et que ferons-nous plus tard ?

Sommaire

Cet article suit le précé­dent, et décrit l’évo­lu­tion de l’ar­chi­tec­ture tech­nique qui y est expo­sée. Pour rappel, nous avons évoqué l’exis­tence de deux projets, dont le premier défi­nis­sait une archi­tec­ture basée sur le pattern CQS (Command Query Sepa­ra­tion) en essayant de s’ap­pro­cher de la métho­do­lo­gie DDD (Domain Driven Design).

Nous allons donc main­te­nant abor­der ce qu’il s’est passé ensuite : la génèse d’un nouveau projet, qui, partant des fonda­tions ainsi créées, va faire évoluer le socle tech­nique.

Contexte

Une fois n’est pas coutume, ce projet est lui aussi la refonte d’un projet exis­tant, initia­le­ment conçu avec Drupal. Il est très simi­laire au premier dans le sens où :

  • Bien que le métier du client soit diffé­rent, il s’agit aussi de suivre et d’ins­truire des demandes clients, de leur créa­tion jusqu’à leur fina­li­sa­tion en passant par la plani­fi­ca­tion et la réali­sa­tion, la nature des demandes change car il s’agit ici de pres­ta­tions d’au­dits et de forma­tions.

  • Lui aussi se décom­pose en deux parties, un front-office destiné aux clients de notre client, et un back-office dédié aux gestion­naires métiers, mais il dispose cepen­dant de deux inter­faces utili­sa­teur supplé­men­taires : une dédiée à des pres­ta­taires de services, et une autre pour des utili­sa­teurs d’un autre site qui se sert de celui-ci comme d’un four­nis­seur d’iden­tité au travers d’un SSO (Single Sign On).

  • Ce projet présente égale­ment des work­flows complexes, cepen­dant au lieu d’avoir un seul espace fonc­tion­nel, il en dispose de 5, soit 5 work­flows tous diffé­rents, et ce sans comp­ter un certain nombre de fonc­tion­na­li­tés secon­daires venant s’ajou­ter.

  • Il est plus riche fonc­tion­nel­le­ment : il intègre un compo­sant de créa­tion de formu­laires en ligne dyna­miques, d’une géné­ra­tion de PDF basée sur un DSL (Domain Speci­fic Language), et de multiples autres outils dédiés aux gestion­naires.

Contrai­re­ment à son grand frère, ce projet ne s’adresse pas à plusieurs centaines de milliers de clients, mais décompte aujour­d’hui quelques milliers d’uti­li­sa­teurs. Les contraintes sur l’in­fra­struc­ture sont bien moindres.

Archi­tec­ture hexa­go­nale

Ports and adap­ters

L’archi­tec­ture hexa­go­nale porte un nom qui fausse la percep­tion réelle de ce patron de concep­tion, il devrait être appelé ports and adap­ters, ce qui désigne son aspect le plus impor­tant : l’in­fra­struc­ture est masquée derrière des inter­faces appe­lées les ports, et les implé­men­ta­tions appe­lées les adap­ters sont dépor­tées dans la couche Infra­struc­ture

Oignon déstructuré
Archi­tec­ture hexa­go­nale | Tange­rine Newt via Unsplash

La notion de couches de l’ar­chi­tec­ture hexa­go­nale est source de confu­sion, car elle englobe deux notions distinctes :

  • Souvent repré­sen­tées de façon concen­triques, les couches Shared Kernel, Domain et Appli­ca­tion sont là pour repré­sen­ter un couplage unidi­rec­tion­nel de dépen­dances : l’ap­pli­ca­tion dépend du domaine métier, qui lui-même dépend d’un outillage spéci­fique dédié au projet (le Shared Kernel). On parle ici du patron de concep­tion archi­tec­ture en oignon (Onion Archi­tec­ture). Les couches Domain et Appli­ca­tion contiennent les ports.

  • Le plus souvent repré­sen­tés de façon hori­zon­tale, la couche Infra­struc­ture, parfois la couche Persis­tence ainsi que d’autres compo­sants et systèmes tiers au Domain métier, sont des notions diffé­rentes des couches de l’oi­gnon. Ensemble, elles contiennent les adap­ters. Dans les faits, il s’agit d’une seule et même couche, sur-décou­pée : la couche Infra­struc­ture pour mettre en valeur les divers compo­sants tech­niques du système.

Image
Architecture hexagonale
Repré­sen­ta­tion visuelle de l’Ar­chi­tec­ture Hexa­go­nale / Ports et Adap­ters

Riches de notre expé­rience, que ce soit au travers des projets que nous avons conçus et réali­sés ou d’au­dits que nous avons menés, il ressort souvent que les projets s’es­sayant à l’ar­chi­tec­ture hexa­go­nale implé­mentent souvent stric­te­ment les diffé­rentes couches théo­riques défi­nies par ce patron de concep­tion :

  • Ce sur-décou­page entre parfois en conflit avec le reste de la concep­tion d’un projet. Par exemple, la couche Appli­­ca­­tion n’a pas toujours de sens.

  • En voulant respec­ter ce décou­page à tout prix, la fron­tière entre Domaine et Appli­ca­tion est parfois floue, ce qui peut créer de la confu­sion mentale.

  • Qui dit plus de couches, dit plus d’in­ter­faces, et par consé­quent un code plus éclaté et plus verbeux.

  • Le décou­page en couche peut parfois amener des algo­rithmes métiers à être décou­pés et implé­men­tés à travers de plusieurs couches, ce qui a pour résul­tat de rendre le code parti­cu­liè­re­ment incom­pré­hen­sible, et diffi­cile à main­te­nir.

Pour rappel l’avan­tage majeur du patron ports and adap­ters est de faci­li­ter la main­te­nance, et non de la complexi­fier : si les fron­tières entre les couches appli­ca­tives sont floues ou mal défi­nies, ou si l’in­té­rêt de maté­ria­li­ser une couche est nul ou non évident, il est alors perti­nent de ne pas l’im­plé­men­ter plutôt que perdre la maîtrise du projet.

Dans notre projet

Dès les premières briques posées dans ce projet, nous avons décidé de partir sur l’archi­tec­ture hexa­go­nale au complet (ce qui, a poste­riori, est une erreur). Voici le décou­plage supplé­men­taire par rapport à la concep­tion du premier projet :

  • Nous avons rajouté un Shared Kernel, un espace de nom conte­nant du code stric­te­ment indé­pen­dant de toute biblio­thèque externe, dont le but est de porter l’ou­tillage trans­verse à tous les Boun­ding Contexts du projet.

  • Le code censé mani­pu­ler des API ou du code externe a été ségré­gué derrière des inter­faces, et leurs implé­men­ta­tions résident désor­mais dans la couche Infra­struc­ture, en suivant scru­pu­leu­se­ment les notions de Ports et Adap­ters, à noter que ce choix est parfois discu­table et dans certains cas regret­table car cela rend plus diffi­cile la navi­ga­tion dans le code du projet pour les déve­lop­peurs.

  • Une couche Persis­tence est ajou­tée, sortie de la couche Infra­struc­ture, et ce pour struc­tu­rer le code, elle contient l’im­plé­men­ta­tion des repo­si­to­ries.

  • La couche User Inter­face (pour inter­face utili­sa­teur) est rajou­tée et sortie du Domain pour décou­pler la dépen­dance aux contrô­leurs de Symfony.

  • Une couche Appli­ca­tion a été ajou­tée en plus de la couche Domain, elle contient les inter­faces et API expo­sées direc­te­ment pour la User Inter­face, et nous verrons plus tard que ce choix n’était pas perti­nent.

  • Afin de rendre le projet plus cohé­rent, et redon­ner ses lettres de noblesse à l’Event Store, nous avons disso­cié les Commandes des Domain Events.

Oignons émincés
Archi­tec­ture en oignon | Wilhelm Gunkel via Unsplash

Ce projet a débuté après une période d’un peu plus d’un an durant laquelle le run de produc­tion et la main­te­nance appli­ca­tive du premier projet s’est dérou­lée. Ceci, nous a permis d’avoir suffi­sam­ment de recul pour réflé­chir et amélio­rer un certain nombre de nos choix précé­dents.

Par exemple, nous avons conservé le bus de messages, et énoncé stric­te­ment dans la docu­men­ta­tion tech­nique les règles d’usage de ce dernier :

  • Une commande est toujours trai­tée de manière atomique, et donne lieu à une unique tran­sac­tion SQL. En cas d’échec, la tran­sac­tion ROLLBACK, et rien ne persiste dans la base de données.

  • Pour respec­ter la règle « une commande égale une et une seule tran­sac­tion », aucune commande ne pourra être trai­tée de façon synchrone pendant le trai­te­ment d’une autre (dans le même contexte d’exé­cu­tion).

  • Durant une tran­sac­tion, zéro ou plusieurs commandes peuvent être pous­sées dans le bus, mais en cas de ROLLBACK de la tran­sac­tion, elles seront annu­lées, et elles ne seront réel­le­ment envoyées dans le bus qu’après un COMMIT.

  • Durant une tran­sac­tion, zéro ou plusieurs Domain Events peuvent être lancés et seront trai­tés de façon synchrone au sein du domaine métier. En cas de ROLLBACK, les trai­te­ments résul­tants de ces événe­ments dispa­raî­tront aussi.

Les évolu­tions

La couche Domaine

La couche domaine de ce projet est très simi­laire à celle du premier projet à l’ex­cep­tion que nous avons une quin­zaine de Boun­ding Contexts, la struc­ture est bien plus décou­pée.

Domaine de Chambord
Le domaine | Wilfried Santer via Unsplash

De plus, pour la confi­gu­ra­tion, nous avons opté pour l’uti­li­sa­tion des Attri­buts appor­tés par la version 8 de PHP. Ainsi, nous avons intro­duit des attri­buts pour :

  • Indiquer qu’une classe est une commande.
  • Indiquer qu’une classe est un modèle.
  • Indiquer qu’une méthode est un command hand­ler.
  • Indiquer qu’une méthode est un event liste­ner.

Ceci nous a permis d’uti­li­ser l’auto-confi­gu­ra­tion du frame­work pour rendre notre work­flow de déve­lop­pe­ment plus effi­cace. De plus, cela permet de décou­pler inté­gra­le­ment le code du domaine de l’in­fra­struc­ture.

Ces attri­buts servent aussi à des tests unitaires, qui prennent la place d’une phase d’ana­lyse statique, pour procé­der à diverses vali­da­tions :

  • Des tests auto­ma­ti­sés de séria­li­sa­tion et déséria­li­sa­tion qui lorsqu’ils échouent font échouer la suite de tests unitaires, et forcent les déve­lop­peurs à aller modi­fier ou créer les Norma­li­zer et Denor­ma­li­zer spéci­fiques dans la couche infra­struc­ture.

  • Des tests qui valident que toutes les commandes ont bien un et un unique hand­ler.

  • Des tests qui véri­fient la présence ou non d’at­tri­buts obli­ga­toires sur les divers compo­sants de la couche Domain afin de s’as­su­rer que la confi­gu­ra­tion du frame­work soit bien effec­tuée.

Les attri­buts peuvent prove­nir de dépen­dances externes car ils n’ont pas d’im­pact sur l’exé­cu­tion du code métier, ils servent seule­ment à des fins de confi­gu­ra­tion. Nous conser­vons un domaine métier complè­te­ment décou­plé de tout intri­ca­tion avec le code de l’in­fra­struc­ture, tout en rappro­chant les éléments de confi­gu­ra­tion du code, rendant sa lecture, son écri­ture et sa main­te­nance plus aisées.

Les Domain Events et l’Event Store

Du fait d’avoir découpé le code métier en un certain nombre de Boun­ding Contexts, il nous fallait un moyen tech­­nique pour permettre la commu­­ni­­ca­­tion entre ces silos : nous avons pour cela utilisé exten­­si­­ve­­ment les Domain Event.

Il a été décidé arbi­trai­re­ment, par conven­tion, qu’à chaque commande trai­tée par un hand­ler, au moins un événe­ment devait être lancé, portant l’in­té­gra­lité des données utiles liées au chan­ge­ment. L’idée d’ori­gine étant, à ce moment là, de ne plus jour­na­li­ser dans l’Event Store des commandes ayant poten­tiel­le­ment échoué, mais des événe­ments ayant vrai­ment eu lieu sur la plate­forme.

On voit ici qu’on redonne à l’Event Store son usage origi­nel, mais malgré ce détail, il reste toujours aussi désuet qu’il ne l’était dans le premier projet, son utili­sa­tion restant toujours et unique­ment à des fins d’au­dit.

Les Domain Events, quant à eux, apportent une vraie fonc­tion­na­lité indis­pen­sable pour ce projet et ont été large­ment utili­sés.

Plus tard lors de l’évo­lu­tion du projet, nous sommes reve­nus en arrière sur la règle qui impose d’avoir un événe­ment pour chaque commande, afin de réduire la verbo­sité et la lour­deur de l’écri­ture du code : les événe­ments ne sont désor­mais plus qu’écrits et lancés que lorsque c’est néces­saire.

La couche Appli­ca­tion

Quand on passe du temps à faire de la lecture sur le sujet sur Inter­net, on se rend compte que le sujet peut être clivant. Il y a deux forces qui s’op­posent :

  • D’un côté, nous avons les gens qui appliquent l’ar­chi­tec­ture hexa­go­nale de façon très théo­rique, et qui bricolent des morceaux de glue là où les aspects pratiques rentrent en conflit avec la théo­rie. C’est de cette façon que nous avons débuté le déve­lop­pe­ment du projet.

  • De l’autre, ceux qui déclarent que l’ar­chi­tec­ture hexa­go­nale n’ap­porte rien d’autre qu’une exagé­ra­tion des abstrac­tions qui parfois s’avèrent inutiles, et qui vont para­doxa­le­ment rendre le projet plus complexe à main­te­nir.

Le projet a évolué vers un juste milieu entre les deux : n’uti­li­ser le décou­page de l’ar­chi­tec­ture hexa­go­nale que là où les béné­fices pour faci­li­ter la main­te­nance sont évidents.

Note impor­tante : on parle ici d’un projet vivant, qui durant toute sa période de run en produc­tion n’a jamais cessé d’évo­luer. Que ce soit pour des évolu­tions mineures ou majeures, une équipe de plusieurs personnes y travaillait à temps plein.

Au début, nous avions donc choisi la première voie. Ne maîtri­­sant pas tout à fait l’archi­­tec­­ture hexa­­go­­nale à ce moment là, nous avons maladroi­­te­­ment maté­­ria­­lisé la couche Appli­­ca­­tion : nous en avons fait un autre names­­pace que la couche Domain. Le contenu de ce names­pace est un miroir du names­pace Domain, dispo­­sant d’un sous names­­pace pour chaque Boun­­ding Context. On y retrou­vait, dans notre cas :

  • Les commandes, envoyées par l’in­ter­face utili­sa­teur.

  • Les événe­ments, qui peuvent être écou­tés à l’ex­té­rieur, bien qu’à ce jour nous n’ayons pas utilisé cette possi­bi­lité.

  • Les query qu’on envoie à la couche domaine ensuite pour récu­pé­rer les enti­tés.

À noter ici qu’au sein même de notre concep­tion, la limite entre Domain et Appli­ca­tion est fragile : le code de l’in­ter­face utili­sa­teur va mani­pu­ler de façon directe les commandes et queries, mais par le biais de ces dernières, elle va aussi se retrou­ver à mani­pu­ler des enti­tés, et donc créer une dépen­dance expli­cite à la couche Domain.

Ce n’est pas idéal, et plus tard dans la vie du projet, nous avons fusionné de nouveau les couches Appli­ca­tion et Domain : ce décou­page tel qu’il exis­tait ne présen­tant aucun avan­tage dans le contexte de ce projet.

La nouvelle couche User Inter­face

Ce projet dispose de plus d’in­ter­faces utili­sa­teurs que le premier qui en a deux. Par consé­quent, nous avons décidé de créer une fron­tière plus franche entre les contrô­leurs et la couche Domain (et Appli­ca­tion ici) : nous avons donc créé une couche supplé­men­taire, la couche User Inter­face.

Selon l’ar­chi­tec­ture d’un projet, l’in­ter­face utili­sa­teur peut être :

  • Soit inté­grée dans le code du backend, ce qui est le cas ici, en utili­sant le frame­work.

  • Soit sous la forme d’une autre appli­­ca­­tion utili­sant des tech­­no­­lo­­gies diffé­­rentes et commu­­niquant avec le domaine au travers du bus de messages, via les commandes ou tout autre moyen tel qu’une REST API par exemple.

Notre projet bien qu’im­plé­men­tant la première solu­tion, combine des aspects de la seconde : les lectures sont synchrones, et utilisent direc­te­ment les inter­faces des repo­si­to­ries, mais toutes les écri­tures passent par le bus de messages.

Les Boun­ding Contexts de cette couche sont les diffé­rentes appli­ca­tions et ne suivent pas scru­pu­leu­se­ment le décou­page du Domain : on retrouve un names­pace par inter­face utili­sa­teur :

  • Un pour le back-office.
  • Un autre pour le front-office.
  • S’en suit un autre pour l’in­ter­face de gestion dédiée aux pres­ta­taires tiers.
  • Et une dernière pour toute la partie four­nis­seur d’iden­tité, le SSO.

Nous avons ici d’ores et déjà un décou­plage de l’in­ter­face utili­sa­teur avec la couche Domaine, d’où la créa­tion de cette nouvelle couche User Inter­face pour maîtri­ser et limi­ter la fuite d’in­ter­faces du Domain dans cette couche de niveau supé­rieur.

Conclu­sion

Ce projet plus riche fonc­tion­nel­le­ment que le premier, et dispo­sant d’un panel de types d’uti­li­sa­teurs plus varié que le premier, s’est très bien prêté à l’exer­cice de l’im­plé­men­ta­tion de l’archi­tec­ture hexa­go­nale. Même si nous avons effec­tué quelques retours en arrière sur des choix d’im­plé­men­ta­tion et de décou­page, le résul­tat est un code très lisible, aisé à main­te­nir, dans lequel les couplages entre compo­sants sont minimes.

Avec quelques années de recul, nous avons statué que l’ar­chi­tec­ture hexa­go­nale était inté­res­sante, mais ne doit pas être implé­men­tée aveu­glé­ment. Bien choi­sir les aspects de ce patron de concep­tion qui sont en accord avec le projet est essen­tiel. En d’autres mots, comme tout patron de concep­tion, il ne doit jamais être implé­menté de manière scolaire, mais adapté, dérivé selon les besoins.

Le futur

La couche Appli­ca­tion

Nous avions découpé notre domaine en deux couches distinctes :

  • La couche Domain, qui contient les enti­tés et la logique métier,

  • La couche Appli­ca­tion qui contient la surface du domaine qui peut être utili­sée par le code des couches supé­rieures, notam­ment l’in­ter­face utili­sa­teur, donc les commandes, les événe­ments et les inter­faces des Read Model.

Doc et Marty à l'air bien ahuri !
– Dites Doc, le futur c’est pour bien­tôt ? – Lais­sez-moi réflé­chir !

À l’usage, nous avons observé que cette distinc­tion, bien que semblant être une bonne idée à l’ori­gine, est en réalité rela­ti­ve­ment inutile. L’archi­tec­ture en oignon, ou hexa­go­nale, ou la Clean Archi­tec­ture dictent bien souvent que toute couche supé­rieure peut atteindre la couche infé­rieure, mais pas l’in­verse (notion de dépen­dance unidi­rec­tion­nelle). Dans ce contexte, avoir une couche domaine divi­sée en deux n’était pas néces­saire, et crée une disso­nance cogni­tive inutile, forçant le déve­lop­peur à faire sans arrêt des allers et venues à deux endroits diffé­rents dans son éditeur de code.

À ce jour, le projet a déjà évolué et nous avons fusionné ces deux couches dans la couche Domain d’ori­gine.

L’Event Store

Avoir un Event Store inutile est… eh bien, inutile. De plus, il stocke aujour­d’hui l’in­té­gra­lité de l’his­to­rique de ce qu’il s’est passé sur les diffé­rentes appli­ca­tions où nous l’avons mis en place.

Journal de bord de René Mouchotte, Juillet 1943
Le jour­nal de bord, véri­table jour­nal des événe­ments.

Cet histo­rique est stocké sous la forme de commandes ou événe­ments séria­li­sés, en JSON le plus souvent. Non seule­ment les lignes de cette table prennent énor­mé­ment de place, mais en plus cette table tend à gros­sir énor­mé­ment. En 5 ans de produc­tion, nous avons près de 10 millions de lignes sur le premier projet, sachant que nous avons une purge qui tourne régu­liè­re­ment pour suppri­mer ce qui est inutile à conser­ver à des fins d’au­dit dans cette table.

Dans le futur, nous devrions rempla­cer cet Event Store par :

  • Une solu­tion d’agré­ga­tion de logs, pour les traces appli­ca­tives.

  • Une plus grande rigueur dans l’écri­ture du code métier pour l’ins­tru­men­ter, jour­na­li­ser tous les aver­tis­se­ments, erreurs, événe­ments métiers impor­tants.

  • Lorsqu’un besoin métier est de jour­na­li­ser des événe­ments qui se passent et de pouvoir le resti­tuer aux utili­sa­teurs, que c’est une fonc­tion­na­lité deman­dée et non un simple besoin d’ob­ser­va­bi­lité, alors il faut dédier des tables d’his­to­riques dans le schéma SQL et des enti­tés métiers pour ces données.

Le bus de commandes

Camion La Poste mais encore plus vert, plus morderne, et plus beau !
Plus vert, plus moderne, plus mâture !

Aujour­d’hui, le compo­sant Messen­ger de Symfony semble avoir atteint une matu­rité suffi­sante pour être utilisé en tant que tel. Nous prévoyons de reve­nir vers lui pour de futurs projets.

Les enti­tés et leur repo­si­to­ries

À terme, il est envi­sagé de tester un projet ayant cette archi­tec­ture avec l’ORM Doctrine pour évaluer ses perfor­mances et confir­mer ou non qu’il soit suffi­sam­ment souple et exten­sible pour nos besoins : là où pour les simples lectures et écri­tures des données, il fera de toute évidence l’af­faire. Nous avons en sus très régu­liè­re­ment le besoin d’écrire des requêtes SQL complexes, (notam­ment pour des tableaux de bord, analy­tiques ou pure­ment métiers) utili­sant des fonc­tion­na­li­tés avan­cées de la base de données Post­greSQL.

Utili­ser un ORM, quel qu’il soit, à terme, devrait nous permettre de deve­nir beau­coup plus effi­cace, car nous n’au­rions plus à écrire les repo­si­to­ries et leur code SQL à la main (bien qu’à ce jour, nous ayons facto­risé de manière effi­cace une majeure partie du code SQL). Ceci au coût de nous rendre dépen­dant de ce dernier et donc de devoir debug­ger un outil qu’on maîtrise bien moins en cas de soucis ou scéna­rio un petit peu moins clas­sique.

Cepen­dant, vouloir reve­nir vers un ORM semble être un choix raisonné, car bien que nous n’ayons pas utilisé Doctrine ORM sur ces trois projets en parti­cu­lier, nous ne l’avons pas aban­donné par ailleurs, et :

  • Doctrine ORM est de toute évidence un produit mature, très stable, acti­ve­ment main­tenu et très bien docu­menté.

  • Le manque de souplesse qu’il pour­rait avoir dans certains cas d’uti­li­sa­tion à la marge est quelque chose de prévi­sible et tout à fait normal. C’est un ORM et par consé­quent, comme tout outil de cette famille, il souffre de défauts inhé­rents à son champ fonc­tion­nel origi­nel. Ce n’est pas un marteau doré mais un outil perfor­mant pour les cas d’uti­li­sa­tion pour lesquels il a été conçu.

  • Rien n’em­pêche d’uti­li­ser un second connec­teur à la base de données à côté de Doctrine, en partant de l’hy­po­thèse qu’il peut réuti­li­ser la même connexion SQL, et donc travailler dans la même session SQL en parta­geant les tran­sac­tions : de ce fait nous pouvons utili­ser le meilleur de chaque outil.

  • La grande force de Doctrine ORM est la gestion des objets et grappes d’objets au travers de son pattern Unit Of Work, qui est parfois critiqué, mais fonc­tionne très bien. Même si certains compor­te­ments à la marge peuvent être ennuyeux, il reste suffi­sam­ment bien docu­menté pour s’en sortir.

Le Domain Driven Design

L’im­plé­men­ta­tion actuelle de ce second projet s’est para­doxa­le­ment éloi­gnée des prin­cipes du Domain Driven Design en compa­rai­son au premier projet : là ou le premier projet inté­grait des méthodes métier sur les enti­tés, le second lui délègue tous les trai­te­ments métiers aux hand­lers.

Dans un monde parfait, les hand­lers devraient s’ef­fa­cer au profit de l’im­plé­men­ta­tion des règles de gestion métier et de leur vali­da­tion, que ce soit le variant ou l’in­va­riant, direc­te­ment dans les enti­tés elles-même.

Ceci présente de nombreux avan­tages :

  • La vali­da­tion devient natu­relle, et n’a plus besoin de compo­sant tiers pour être implé­men­tée : une simple gestion d’er­reur via des excep­tions peut suffire dans la majo­rité des cas.

  • De ce fait, jamais une entité ne pourra vivre en mémoire dans un état inco­hé­rent.

  • Les règles de gestion métier sont implé­men­tées direc­te­ment dans l’en­tité qui en jouit, et par consé­quent le code n’est plus explosé dans diffé­rents compo­sants.

  • Il n’y a plus besoin de créer un envi­ron­ne­ment virtuel incluant un bus de message en mémoire et une implé­men­ta­tion de la couche persis­tance pour écrire les tests fonc­tion­nels : les enti­tés se suffisent à elles-mêmes et l’écri­ture des tests fonc­tion­nels devient natu­relle.

Conclu­sion

Cet article a été rédigé il y a main­te­nant plus d’un an, et depuis de nombreux sujets ont été réflé­chis par notre équipe.

On notera tout d’abord qu’après une phase d’ex­pé­ri­men­ta­tion, nous avons décidé de reve­nir vers Doctrine ORM, car utilisé tel que docu­menté, il offre un stabi­lité éton­nante, et ce même avec des rela­tions entre objets complexes. Il permet donc aisé­ment d’im­plé­men­ter le DDD plei­ne­ment, c’est à dire en incluant tout le code métier qu’on retrouve actuel­le­ment dans nos hand­lers de commande direc­te­ment sous la forme de méthodes sur les enti­tés. Ceci pourra être le sujet d’un futur article.

Les projets étant encore en main­te­nance ont conti­nué d’évo­luer, prin­ci­pa­le­ment dans le sens de la simpli­fi­ca­tion et de la ratio­na­li­sa­tion de l’ou­tillage commun, afin de les rendre plus main­te­nables. Ceci veut dire que notre modèle qui tendait vers l’archi­tec­ture hexa­go­nale et le Domain Driven Design a subti­le­ment évolué, pour s’éloi­gner de l’im­plé­men­ta­tion scolaire des couches de l’archi­tec­ture hexa­go­nale au profit d’une version simpli­fiée et adap­tée à chaque projet, selon ses contraintes.

Les liens

Liens des projets open source utili­sés dans les projets

Nouveaux outils déve­lop­pés depuis

Formations associées

Formation Symfony

Formation Symfony Initiation

Paris Du 28 au 30 mai 2024

Voir la formation

Formations Outils et bases de données

Formation PostgreSQL

Paris Du 5 au 6 juin 2024

Voir la formation

Actualités en lien

Image
Symfony
11/04/2024

Access Control : une biblio­thèque PHP pour gérer des droits d’ac­cès

Suite à un projet de gestion métier opéra­tion­nel dont la durée de vie et la main­te­nance sont à long termes, nous avons expé­ri­menté un passage de celui-ci sur l’archi­tec­ture hexa­go­nale et la clean archi­tec­ture.

Voir l'article
Image
Encart Article Symfony Pierre
13/02/2024

Itéra­tions vers le DDD et la clean archi­tec­ture avec Symfony (1/2)

Pourquoi et comment avons nous fait le choix de faire évoluer la concep­tion de nos projets Symfony, et quelles erreurs avons-nous faites ?

Voir l'article
Image
DbToolsBundle + Symfony = ❤️
06/02/2024

Découvrez le DbToolsBundle

L'équipe PHP est fière de vous présenter son nouveau bundle à destination des développeurs Symfony : sauvegardez, restaurez et anonymisez simplement vos bases de données !

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus