L’archi Hexa est-ce archi bien❓
(à répeter 100 fois sans se tromper 🤡)
(The english version will be available soon)
:emoji_intro_qui_existe_pas: Introduction :
✍🏿 Le but de cet article est de donner une vue d’avion sur ce qu’est l’architecture hexagonale et de faire un retour d’expérience sur son utilisation dans un contexte professionnel.
📚 Définition :
L’architecture hexagonale est une architecture logicielle créée par Alistair Cockburn en 2005. Elle repose sur des règles et des principes simples.
🎯 L’objectif est que votre application puisse être exécuté facilement par différents points d’entrés, 1 script bash, 1 requête HTTP, 1 test… Le tout sans se soucier de services externe (base de données, message broker…) pour fonctionner.
💡 Pour y arriver il faut établir des frontières 🔐 et rendre le code qui répond au besoin métier “agnostique” de tout système extérieur, il doit être indépendant de toute infrastructure.
🚪 Concrètement le code métier doit pouvoir exister et fonctionner sans avoir besoin d’une base de données précise, d’être appelé dans 1 contexte HTTP ou même via une ligne de commande… Idéalement tu peux coder toute ton application et choisir l’infrastructure à la fin du projet 😅.
On va donc pouvoir isoler le code qui a le plus de plus value et se focus sur celui-ci et c’est exactement ce vers quoi tend le Domain Driven Design, c’est pour cela que très souvent l’architecture hexagonale et le DDD sont implémentés ensemble.
Bon revenons à l’architecture hexagonale, je vous ai parlé de frontières mais des frontières entre qui et quoi❓
Nous allons établir des frontières entre :
- notre modèle
- le système qui appelle et manipule notre modèle
- les services externes dont peut avoir besoin notre application
Alors il y a différents naming pour nommer ces 3 composants mais partons sur ceux-ci :
- Domain qui contient notre modèle, les ports/interfaces pour manipuler nos données, les exceptions lié au domain et les handlers.
- Application qui contient la porte d’entrée de notre application, c’est depuis cette partie qu’on executera les différentes actions de notre application
- Infrastructure qui contient les services externes dont notre application peut avoir besoin pour fonctionner
Pour que les composants puissent communiquer entre eux on utilise des ports et des adapters et on s’appuie sur le principe d’inversion de dépendance (le “D” dans l’acronyme SOLID).
Voici un schéma extrait de ce repository qui montre à quoi ressemble 1 architecture hexagonale
Dans cet exemple la partie gauche en vert c’est ce qu’on appelle Application la partie au centre est la partie Domain qui contient donc notre métier et la partie bleue à droite est l’Infrastructure.
💻 Let’s code
Voyons une implémentation concrète, pour mon exemple je dois exposer un endpoint API qui permet de créer un utilisateur.
Je vais créer donc mon endpoint API avec 1 payload qui contiendra les informations envoyées par l’utilisateur de l’API, mon modèle User, 1 repository pour pouvoir enregistrer mon User et un handler qui va concrètement enregistrer mon User.
Je commence par mon coeur de métier, celui qui a le plus de plus value, ce qui se situe dans le namespace Domain
1- UserId
J’utilise 1 Value object pour store l’id d’un User
2- Mon Model
3- Mon Repository
4- L’objet Input qui transportera les données valides pour créer un User
5- L’objet Output qui transportera les données du User qui viens d’être crée
6- Le handler responsable de créer le User
⚠️ Quelques remarques importantes ici :
- 1️⃣ Mon handler prend 1 objet
CreateAUserInput
en paramètre, il ignore donc complètement le contexte dans lequel il est appelé, ça peut être 1 requête HTTP comme dans notre exemple, mais ça peut aussi être un simple test unitaire ou bien même un script bash… - 2️⃣ J’utilise l’inversion de dépendance pour enregistrer mon User via
UserRepositoryInterface
, mon handler n’a donc aucune idée de l’implémentation concrète du repository qui enregistrerera mon User (ça peut être une base de donnée, un call API vers elasticsearch, dans un tableau PHP pour faire des tests…) - 3️⃣ Je retourne un simple objet qui transporte des informations, libre à la personne qui utilise le handler de les utiliser.
- 4️⃣ J’ai mis la logique dans le namespace
Domain
mais il y a discussion car il y a des avis pour le mettre dans le namespaceApplication
Passons côté Infrastructure
7- Implémentation concrète de mon repository
Dans notre exemple on utilise un repository InMemory, si il y a d’autres services externes (envoi de mail, repository branché à 1 base de données…) c’est ici que nous créons ces objets
Passons côté Application
8- Le Payload qui servira de DTO pour récupérer les data envoyées à notre API. Je mets des asserts dessus pour pouvoir valider les données soumises.
9 - L’endpoint API pour pouvoir ajouter un User
On récupère l’output retournée par le handler et on le retourne dans un tableau qui sera transformée en Json 👌
10 - 🎁 Bonus on doit pouvoir créer 1 User depuis 1 commande Symfony
Avec notre approche on a juste besoin de passer un objet CreateAUserInput
👣 Sortir du CRUD classique
J’ai travaillé sur une multitude de projets avec des problématiques et des tailles d’equipe différentes et clairement travailler en architecture hexagonale facilite grandement les choses. Je vous liste les principaux avantages que j’ai vu :
- Le développement est standardisé (on se pose pas 36 questions sur l’implémentation) 🎯
- Review plus simple a analyser, on sait qui fait quoi et donc on se focus sur ce qui nous importe réellement, la logique métier 🤑
- Plus facile a debug et à corriger 🐛
- Plus simple à tester ✅
- Plus simple à maintenir 🕸
- Code plus stable car on ne dépens pas de contexte ⛷
- L’utilisation de ports rend le code facilement réutilisable, il suffit de respecter le contrat ✂️
- Les dépendances externes sont facilement interchangeables 🔁
- Code plus lisible et donc l’onboarding est facilité 💡
Les désavantages que j’ai vu :
- La redondance de classe 🗣
- La nécessité d’utiliser une librairie pour l’injection de dépendance sinon c’est ingérable 🥋
- La plupart des applications web ne changent pas de framework ou de base de données, faut-il anticiper quelque chose qui n’arrivera pas❓
- Beaucoup de classe intermédiaires, faut parfois tirer lonnnnnngtemps le fil pour arriver au Handler 🧶
- L’architecture hexagonale ne vous garantit pas de faire du clean code 🧤
- Le manque de “liberté” dans l’implémentation peut provoquer une certaine lassitude 😅
🏔 Comment s’y mettre ?
“c’est en codant qu’on devient coderon” 🔥
💡 En terme de “règle” à respecter l’architecture hexagonale est vraiment simple suivre, en terme de connaissance il faut à minima être à l’aise avec la programmation orientée objet, les notions d’interfaces, de Single responsability et d’inversion de dépendance.
✨ Pour vous faire la main il faut vous jeter à l’eau 🌊, implémentez cette architecture sur un side project ou faîtes une petite application (un blog ou une API Rest).
💻 Dans un contexte professionnel c’est difficile et “coûteux” de switcher d’architecture sur du codebase existant néanmoins vous pouvez le mettre en place sur de nouveaux développements.
📖 Pour poursuivre sur l’architecture hexagonale
⚠️ Je ne suis pas un expert de l’archi hexa et cet article est largement insuffisant pour t’en contenter, il y a beaucoup d’autres notions et choses à explorer sur le sujet. C’est pourquoi, si tu souhaites en savoir plus je te recommande de lire/regarder les liens qui suivent :
- Excellente conférence sur la clean architecture au forum PHP 2019
- Ce repository 😍
- Ce petit article simple et rapide à lire
- En savoir plus sur les ports et adapters
- Project structure
🎬 Voilà voilà, merci de m’avoir lu, partagez à fond l’article et laissez 50 👏 ! 😊