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

Nantes Du 25 au 27 mars 2025

Voir la formation

Formations Outils et bases de données

Formation PostgreSQL

Nantes Du 11 au 13 décembre 2024

Voir la formation

Actualités en lien

Image
Encart blog DBToolsBundle
18/07/2024

DbTools­Bundle : sortie de la version 1.2

Décou­vrez les nouveau­tés de cette nouvelle version ainsi que les fonc­tion­na­li­tés à venir de la prochaine version majeure.

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

DbToolsBundle : l'outil Symfony pour anonymiser vos bases de données

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