Architecture Hexagonale : trois principes et un exemple d’implémentation

Documentée en 2005 dans son blog par Alistair Cockburn, l’Architecture Hexagonale est une architecture logicielle qui a beaucoup d’avantages et connaît depuis 2015 un regain d’intérêt.

L’intention originale de l’Architecture Hexagonale est :

Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases.

Soit en français :

Permettre à une application d’être pilotée aussi bien par des utilisateurs que par des programmes, des tests automatisés ou des scripts batchs, et d’être développée et testée en isolation de ses éventuels systèmes d’exécution et bases de données.

Pour explorer l’intérêt qu’il y a à piloter une application par des tests automatisés, et à la développer et tester en isolation de sa base de données par exemple, je vous conseille cette série sur la pyramide des tests : La pyramide des tests par la pratique.

La promesse est donc assez séduisante, et elle a un autre effet bénéfique : elle permet d’isoler le cœur de métier d’une application et de tester automatiquement son comportement indépendamment de tout le reste. Pas étonnant que cette architecture ait tapé dans l’oeil des praticiens de Domain-Driven Design (DDD). Attention, DDD et l’architecture hexagonale sont deux notions bien distinctes qui peuvent se renforcer mutuellement mais qui ne sont pas nécessairement utilisées ensemble. Mais ça, c’est un sujet pour une autre fois !

Pour finir, cette architecture n’est pas très compliquée à mettre en place car elle s’appuie sur des règles et principes simples et peu nombreux. Je vous propose d’explorer ces principes pour voir ce que ça implique concrètement.

Principes de l’Architecture Hexagonale
Détail : Comment on organise le code à l’intérieur et à l’extérieur ?
Détail : Au Runtime
Détail : Inversion de Dépendances à droite
Détail : Pourquoi une Interface à gauche ?
Tester en Architecture Hexagonale
Pour aller plus loin
Références

Principes de l’Architecture Hexagonale

L’architecture hexagonale s’appuie sur trois principes et techniques :

  • Séparer explicitement User-Side, Business Logic et Server-Side
  • Les dépendances vont vers la Business Logic
  • On isole les frontières par des Ports et Adapters

Note sur le vocabulaire : dans toute la suite de l’article, on utilisera les expressions User-Side, Business Logic et Server-Side. Elles viennent de l’article original et son expliquées dans la section ci-dessous.

Principe : Séparer User-Side, Business Logic et Server-Side

Le premier principe est de séparer explicitement le code en trois grandes zones formalisées.

À gauche, la zone User-Side

C’est le côté par lequel l’utilisateur ou les programmes extérieurs vont interagir avec l’application. On y trouve le code qui permet ces interactions. Typiquement, votre code d’interface utilisateur, vos routes HTTP pour une API, vos sérialisations en JSON à destination de programmes qui consomment votre application sont ici.

C’est le côté où l’on retrouve les acteurs qui pilotent la Business Logic.

Note : Alistair Cockburn parle aussi de Left Side.

Au centre, la Business Logic

C’est la partie que l’on veut isoler de ce qui est à gauche et à droite. On y trouve tout le code qui concerne et implémente la logique métier. Le vocabulaire métier et la logique purement métier, ce qui se rapporte au problème concret que résout votre application, tout ce qui en fait la richesse et la spécificité est au centre. Dans l’idéal, un expert du métier qui ne sait pas coder pourrait lire un bout de code de cette partie et vous pointer une incohérence (true story, ce sont des choses qui pourraient vous arriver !).

Note : C’est l’Hexagone dont parle Alistair Cockburn ! Il parle aussi de Center pour cette zone.

À droite, la zone Server-Side

C’est ici qu’on va retrouver ce dont votre application a besoin, ce qu’elle pilote pour fonctionner. On y trouve les détails d’infrastructure essentiels comme le code qui interagit avec votre base de données, les appels au système de fichier, ou le code qui gère des appels HTTP à d’autres applications dont vous dépendez par exemple.

C’est le côté où l’on retrouve les acteurs qui sont pilotés par la zone Business Logic.

Note : Alistair Cockburn parle aussi de Right Side.

Les principes qui suivent vont permettre de mettre en pratique cette séparation logique entre User-Side, Business Logic et Server-Side.

Pourquoi c’est important ?

Une première caractéristique importante de cette séparation est qu’elle sépare les problèmes. À tout moment, on peut choisir de se concentrer sur une seule logique, presque indépendamment des deux autres : la logique applicative, la logique métier, ou la logique infrastructure. On les comprend plus facilement sans les mélanger, et les contraintes de chaque logique a moins d’impact sur les autres.

Une autre caractéristique est qu’on met la logique métier en avant dans notre code. On peut l’isoler dans un répertoire ou un module pour la rendre explicite pour tous les développeurs. On peut la définir, la raffiner et la tester sans embarquer la charge cognitive du reste du programme. C’est important car, au final, c’est la compréhension du métier par les développeurs qui part en production.

Et, pour finir, en terme de tests automatisés (comme on va le voir plus bas), on va réussir à tester avec un effort raisonnable :

  • Toute la Business Logic unitairement,
  • L’intégration entre User-Side et Business Logic – sans le Server-Side
  • L’intégration entre Business Logic et Server-Side – sans le User-Side

Illustration : un petit exemple d’application

Pour illustrer plus concrètement ces principes, on va reprendre le petit exemple utilisé lors de la soirée Alistair in the “Hexagone”, proposée en 2017 par Thomas Pierrain (@tpierrain) et Alistair Cockburn (@TotherAlistair) en personne. Note : vous trouverez les vidéos et le code de l’événement en fin d’article.

L’objectif de cette petite application est de fournir un programme en ligne de commande qui écrit des poèmes dans la sortie standard de la console.

Exemple d’utilisation souhaitée :

$ ./printPoem
Here is some poem:
I want to sleep
Swat the files
Softly, please.
-- Masaoka Shiki (1867 - 1902)
Type enter to exit...

Pour illustrer correctement les trois zones (User-Side, Business Logic, Server-Side), cette application ira chercher des poèmes dans un système extérieur : un fichier. On pourrait aussi brancher cette application sur une base de données, les principes seraient identiques.

Dans ce contexte, comment appliquer ce premier principe, à savoir la séparation en trois zones ? Comment répartir le code à gauche (ce qui pilote), au centre (le coeur de métier) et à droite (ce qui est piloté) ?

Côté User-Side

Du point de vue de l’utilisateur, le programme se présente comme une application console. Donc la notion de console sera à gauche, du côté User-Side. C’est par la console que l’utilisateur va piloter le métier.

Côté Server-Side

Techniquement, dans notre cas les poèmes sont stockés dans un fichier. Cette notion de fichier va se retrouver à droite, du côté Server-Side. Le métier va effectuer la demande de ses poèmes en pilotant ce côté droit, concrètement implémenté par un PoetryLibraryFileAdapter.

Ici, comme évoqué plus haut, on peut facilement interchanger notre source de poèmes (un fichier, une base de données, un web service…). L’implémentation réelle de la source sous forme de fichier est donc un détail technique (aussi appelé un détail technique d’implémentation)

La Business Logic

Notre cœur de métier dans ce cas, ce qui a de la valeur pour l’utilisateur, c’est la notion de lire des poèmes. On peut matérialiser cette notion dans le code avec une classe PoetryReader par exemple.

Interaction User-SideBusiness Logic

Du point de vue métier, peu importe que la demande vienne d’une application console ou autre, c’est un détail technique dont on souhaite pouvoir s’abstraire. C’est précisément une des intentions initiales : “être piloté aussi bien par un utilisateur que par des tests”. Il n’y a donc pas de notion de console dans la Business Logic. Ce que permet notre application en revanche, du point de vue de l’utilisateur (= le service qu’elle lui rend) c’est de demander des poèmes. C’est donc cette notion que l’on va retrouver dans la Business Logic (matérialisé par IRequestVerses) et qui va permettre au côté User-Side d’interagir avec la Business Logic.

Interaction Business LogicServer-Side

De même, du point de vue Business Logic, peu importe que les poèmes viennent d’un fichier ou d’une base de données, on souhaite pouvoir tester notre application indépendamment des systèmes extérieurs. Pas de notion de fichier dans la Business Logic donc. Pour fonctionner, le métier a tout de même besoin d’obtenir les poèmes. On retrouve donc cette notion d’obtenir des poèmes dans la Business Logic sous la forme de l’interface IObtainPoems. C’est cette notion d’obtenir des poèmes qui va permettre au métier d’interagir avec la zone Server-Side.

Note : à partir d’ici, quand vous lisez les schémas, vous pouvez commencer à observer les flèches qui montrent les relations entre les classes. Une flèche pleine représente une interaction de type appel ou composition. Et une flèche sans remplissage représente une relation d’héritage (comme en UML). Mais pas besoin de tout analyser tout de suite, on va l’explorer en détail plus loin.

Note : les noms IRequestVerses et IObtainPoems représentent bien des interfaces, on en parlera dans un principe à suivre. Pour l’anecdote, la convention de commencer le nom d’une interface par un “i” n’est plus à la mode mais Thomas Pierrain lit les noms des interfaces comme des phrases à la première personne du singulier. IRequestVerses se lit : I request verses par exemple. J’aime bien cette idée.

Principe : les dépendances vont vers l’intérieur

C’est un principe essentiel pour arriver à l’objectif. On a déjà commencé à le voir lors du principe précédent.

Principe : Les dépendances vont vers la Business Logic

Pour qu’on puisse piloter le programme aussi bien par la console que par des tests, on ne trouve pas de notion de console dans la Business Logic. Donc la Business Logic ne dépend pas du côté User-Side, c’est le côté User-Side qui dépend de la Business Logic. Le côté User-Side (ConsoleAdapter) dépend de la notion de demande de poèmes, IRequestVerses (qui définit un mécanisme générique de “demande de poèmes” de la part de l’utilisateur).

De même, pour qu’on puisse tester le programme indépendamment de ses systèmes extérieurs, la Business Logic ne dépend pas du côté Server-Side, c’est l’inverse : c’est la zone Server-Side qui dépend de la Business Logic, à travers la notion d’obtenir des poèmes, IObtainPoems. Techniquement une classe du côté Server-Side va hériter de l’interface définie dans la Business Logic et l’implémenter, on va le voir en détails plus loin pour parler d’inversion de dépendances.

Intérieur & Extérieur

Si on voit les relations de dépendance (<<dépend de…>>) comme des flèches, ce principe définit donc la Business Logic au centre comme un intérieur, et tout le reste comme un extérieur (voir figure). On retrouve régulièrement ces notions d’intérieur et d’extérieur quand on discute d’architecture hexagonale. Ça peut même être le point fondamental à retenir et transmettre : les dépendances vont vers l’intérieur.

Autrement dit, tout dépend de la Business Logic, la Business Logic ne dépend de rien. Alistair Cockburn insiste sur cette démarcation entre intérieur et extérieur, qui est plus structurante que la différence entre User-Side et Server-Side pour résoudre le problème initial.

Principe : on isole les frontières par des interfaces

Pour résumer, le code User-Side pilote le code métier à travers une interface (ici IRequestVerses) définie dans le code métier. Et le code métier pilote le code Server-Side à travers une interface définie aussi dans le code métier (IObtainPoems). Ces interfaces jouent le rôle d’isolants explicites entre intérieur et extérieur.

Une Métaphore : Ports & Adapters

L’architecture hexagonale utilise la métaphore de ports et d’adapters pour représenter les interactions entre intérieur et extérieur. L’image est que la Business Logic définit des ports, sur lequel on peut brancher de manière interchangeable toutes sortes d’adapters, à condition qu’ils suivent la spécification définie par le port.

Par exemple, on peut imaginer un port de la Business Logic sur lequel on va brancher soit une source de donnée codée en dur pendant un test unitaire, soit une vraie base de données dans un test d’intégration. Il suffit de coder les implémentations et les adapters correspondants côté Server-Side, la Business Logic n’est pas impacté par ce changement.

Ces interfaces définies par le code métier, qui isolent et permettent les interactions avec l’extérieur sont donc les ports de la métaphore Ports & Adapters. Note : comme mentionné ci-dessus, les ports sont définis par le métier, ils sont donc à l’intérieur.

Les adaptateurs, eux, représentent le code à l’extérieur qui fait la glue entre le port et le reste du code applicatif ou infrastructure. Ici, les adaptateurs sont respectivement ConsoleAdapter et PoetryLibraryFileAdapter. Ces adaptateurs sont à l’extérieur.

Autre Métaphore : l’Hexagone

Une autre métaphore qui a donné son nom à cette architecture et celle de l’hexagone, comme on le voit sur la figure précédente. Pourquoi un hexagone ? La raison principale est que c’est une forme facile à dessiner qui laisse la place pour représenter plusieurs ports et adapters sur les schémas. Et il se trouve que même si l’hexagone est assez anecdotique au final, l’expression Hexagonal Architecture est plus populaire que Ports & Adapters Pattern. Sans doute parce-que ça sonne mieux ?

La partie théorique est terminée, il n’y a pas d’autres principes : pour tout le reste on est libre.

Détail : Comment on organise le code à l’intérieur et à l’extérieur ?

À part les principes vus ci-dessus, on est totalement libre d’organiser le code à l’intérieur de chaque zone exactement comme on le veut.

Concernant le code métier par exemple, l’intérieur, une bonne idée est de choisir d’organiser ses modules (ou répertoires) en fonction de la logique métier.

Une organisation à éviter est de regrouper les classes par types. Par exemple le répertoire des “ports”, ou le répertoire des “repositories” (si vous utilisez ce pattern), ou le répertoire des “services”. Pensez 100 % métier dans votre code métier, y compris pour l’organisation de vos modules ou répertoires ! L’idéal est de pouvoir ouvrir un répertoire ou un module de la logique métier et de comprendre tout de suite les problèmes métier que votre programme résout; plutôt que de ne voir que des répertoires “repositories”, “services”, ou autre “managers”.

Voir aussi à ce sujet :

Détail : Au Runtime

Comment instancier tout ça pour satisfaire les dépendances au runtime au juste ? Si vous utilisez un framework d’injection de dépendances, vous n’aurez peut-être pas besoin de vous poser cette question. Mais je pense que pour bien comprendre l’architecture hexagonale, c’est intéressant de voir ce qui se passe au démarrage de l’application. Et pour ce faire, de ne pas utiliser de framework d’injection de dépendances au moins le temps de cet article.

Par exemple, voilà comment on écrira le point d’entrée de l’application si on instancie tout à la main :

class Program
{
    static void Main(string[] args)
    {
        // 1. Instantiate right-side adapter(s) ("I want to go outside the hexagon")
        IObtainPoems fileAdapter = new PoetryLibraryFileAdapter(@".\Rimbaud.txt");

        // 2. Instantiate the hexagon
        IRequestVerses poetryReader = new PoetryReader(fileAdapter);

        // 3. Instantiate the left-side adapter(s) ("I want ask/to go inside the hexagon")
        var consoleAdapter = new ConsoleAdapter(poetryReader);

        System.Console.WriteLine("Here is some...");
        consoleAdapter.Ask();

        System.Console.WriteLine("Type enter to exit...");
        System.Console.ReadLine();
    }
}

L’ordre d’instanciation est typique, de droite à gauche :

  1. On instancie d’abord le côté Server-Side, ici le fileAdapter qui va lire le fichier.
  2. On instancie la classe du Business Logic qui va être pilotée par l’application, le poetryReader dans lequel on injecte le fileAdapter par injection dans le constructeur.
  3. On instancie le côté User-Side, le consoleAdapter qui va piloter le poetryReader et écrire dans la console. Ici on injecte à son tour le poetryReader dans le consoleAdapter par injection dans le constructeur.

On avait pourtant dit que l’intérieur ne devait pas dépendre de l’extérieur ! Alors pourquoi est-ce qu’on injecte le fileAdapter, qui est du code venant du Server-Side, dans le poetryReader qui est du code appartenant à la Business Logic ?

On peut le faire car, en regardant les schémas et le code, en plus d’être un PoetryLibraryFileAdapter (côté Server-Side), le fileAdapter est aussi une instance de IObtainPoems par héritage.

En pratique le PoetryReader ne dépend donc pas de PoetryLibraryFileAdapter mais bien de IObtainPoems, qui est bien définit dans la Business Logic. On peut le vérifier en regardant la signature de son constructeur.

public PoetryReader(IObtainPoems poetryLibrary)
{
    this.poetryLibrary = poetryLibrary;
}

PoetryLibraryFileAdapter et PoetryReader sont donc faiblement couplés.

Détail : Inversion de Dépendances à droite

Le fait que le fileAdapter dépende du métier pour sa définition (dépendance par héritage ici), mais qu’au runtime le poetryReader puisse contrôler en pratique une instance de fileAdapter est un cas classique d’inversion de dépendances.

En effet, sans l’interface IObtainPoems, le code métier dépendrait du Server-Side pour sa définition, ce qu’on veut éviter :

L’interface permet d’inverser le sens de cette dépendance :

En plus de rendre le métier indépendant des systèmes extérieurs, cette interface à droite permet de satisfaire le fameux D de SOLID, ou Dependency Inversion Principle. Ce principe dit :

  1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d’abstractions.
  2. Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.

Si on n’avait pas l’interface, on aurait un module de haut niveau (la Business Logic) qui dépendrait d’un module de bas niveau (le Server-Side).

Note : pour les interactions entre côté gauche et code métier, la dépendance est naturellement dans le bon sens.

Cette différence dans l’implémentation des interactions est liée à la différence entre les relations User-SideBusiness Logic et Business LogicServer-Side. Rappel : la zone User-Side pilote (drives) la Business Logic, et le côté Server-Side est piloté (driven by) par la Business Logic.

Détail : Pourquoi une Interface à gauche ?

Puisque les dépendances entre User-Side et Business Logic sont déjà dans le bon sens, le rôle de l’interface IRequestVerses n’est pas d’inverser les dépendances.

Pourtant, elle a quand même un intérêt : celui de limiter explicitement la surface de couplage entre le code User-Side et le code Business Logic.

En effet, en pratique la classe PoetryReader peut avoir d’autres méthodes que celles de l’interface IRequestVerses. Il est important que le ConsoleAdapter n’en ait pas connaissance.

Et il se trouve que c’est aussi aligné avec un autre principe de SOLID, Interface Segregation Principle.

Clients should not be forced to depend on methods they do not use.

Mais une fois qu’on a saisi l’intention, si un port vers le côté gauche n’a qu’une méthode, et que son implémentation n’a qu’une méthode comme dans notre exemple, est-ce que l’interface est réellement nécessaire ? À fortiori dans un langage dynamique qui va fonctionner par duck typing au final ?

On peut répondre par une question : qu’en pense votre équipe ? Est-ce que l’objectif d’isolation est bien clair pour tout le monde, pas besoin d’interface pour ne serait-ce que déclencher une conversation ? À vous de décider ensemble.

Tester en Architecture Hexagonale

Un bénéfice important de cette architecture logicielle est qu’elle facilite l’automatisation des tests, ce qui fait partie de son intention initiale.

Et on va retrouver la notion de “qui pilote qui”, soit la question : “who is in charge or triggers the conversation?” qui va nous aide à structurer nos tests automatiques.

Comment remplacer le code du côté User-Side ?

Dans le cas général, le rôle du code de gauche peut être directement joué par le framework de test. En effet, le code de test peut directement piloter le code de la logique métier.

Note : la figure illustre un test d’intégration car la partie droite n’est pas remplacée. On peut aussi la remplacer, voir ci-dessous.

Comment remplacer le code du côté Server-Side ?

Le code de droite doit être piloté par le métier. En général, si on souhaite écrire un test unitaire, on le remplace par un mock ou toute autre forme de test double en fonction de ce qu’on veut tester.

Objectif atteint !

Permettre à une application d’être indifféremment pilotée par des utilisateurs, des programmes, des tests automatisés ou des scripts batchs, et d’être développée et testée en isolation de ses éventuels systèmes d’exécution et bases de données.

Et attention ! Ça n’empêche pas de tester automatiquement votre code User-Side et Server-Side, tout code mérite d’être testé automatiquement. Sur ce sujet, je vous renvoie à nouveau vers la série La pyramide des tests par la pratique.

Et en effet, en combinant ce qu’on remplace ou pas, on voit qu’avec cette architecture on peut tester ce qu’on voulait :

  • Toute la Business Logic unitairement,
  • L’intégration entre User-Side et Business Logic, indépendamment du côté Server-Side
  • L’intégration entre Business Logic et Server-Side, indépendamment du côté User-Side

Pour aller plus loin

Parlez-en en équipe, qui sait déjà faire chez vous ?

Lancez-vous, expérimentez en vrai, sur votre code. Un petit projet perso par exemple, ou un petit projet avec votre équipe. Qu’est-ce qui est facile pour vous, qu’est-ce qui est difficile ?

Voici quelques questions supplémentaires que vous pourrez vous poser pendant l’implémentation :

  • Un port peut n’avoir qu’une seule méthode, ou regrouper plusieurs méthodes. Qu’est-ce qui est logique dans votre cas ?
  • Même quand il suit bien les principes de dépendances, le code n’est pas nécessairement séparé en trois modules ou répertoires ou packages ou namespaces explicites. Comme dans le code de Thomas Pierrain, j’ai vu plusieurs fois la Business Logic rangée dans un répertoire « Domain », et le code User-Side et Server-Side rangé dans un répertoire « Infrastructure« . Dans son exemple, l’intérieur est rangé dans le namespace HexagonalThis.Domain et l’extérieur regroupé dans le namespace HexagonalThis.Infra.

Rappel : il n’y a pas de silver bullet. L’architecture hexagonale est un bon compromis complexité / puissance, et c’est aussi un très bonne manière de découvrir les sujets qu’on a abordés. Mais ce n’est qu’une solution parmi d’autres. Pour des cas simples, c’est peut être trop compliqué, et pour des cas compliqués, c’est peut être trop simple. Et il y a d’autres architectures logicielles qui valent le coup d’être explorées. Par exemple, la Clean Architecture va plus loin dans la formalisation et l’isolation (avec un zeste de SOLID supplémentaire). Ou bien dans un axe différent mais compatible, CQRS permet de mieux séparer lectures et écritures.

Références

Les vidéos de l’événement Alistair in the « Hexagone » sont ici.

Le code de l’événement est sur le github de Thomas Pierrain.

Lisez également ces bons articles sur le sujet :

Pour finir, merci à Thomas Pierrain pour m’avoir autorisé à réutiliser son exemple de code, et merci pour les suggestions et les relectures à : Etienne Girot, Jérôme Van Der Linden, Jennifer Pelisson, Abel André, Nelson Da Costa, Simon Renoult, Florian Cherel Enoh, Mathieu Laurent, Mickael Wegerich, Bertrand Le Foulgoc, Marc Bojoly, Jasmine Lebert, Benoît Beraud, Jonathan Duberville et Eric Favre.

Note de mise à jour : dans une première version de l’article, nous avions utilisé les mots Application, Domain et Infrastructure en remplacement de User-Side, Business Logic et Server-Side. Nous sommes revenus au vocabulaire original car cette substitution était ambiguë et inutile.

29 commentaires sur “Architecture Hexagonale : trois principes et un exemple d’implémentation”

  • Bonjour, Bravo pour l'article qui est très clair. Il y a cependant un élément qui me gène dans l'implémentation fait côté Application, c'est la dépendance créée avec l'Infrastructure et qui n'apparaît pas sur les diagrammes.
  • Grand merci pour cet article Sébastien ! C'est très clair et agréable à lire ! Hâte d'essayer sur un projet :)
  • Bonjour Merci ! Un des meilleurs post que j'ai vu sur la question Je conseille la vidéo : "Coder sans peur du changement, avec la "même pas mal !" architecture hexagonale" https://www.youtube.com/watch?v=wZ7cxcU4iPE
  • Super explication! Merci! Une erreur semble s'être glissée... : il est écrit : Et, pour finir, en terme de tests automatisés (comme on va le voir plus bas), on va réussir à tester avec un effort raisonnable : . Tout le Domain unitairement, . L’intégration entre Application et Domain indépendamment du côté Infrastructure [côté Application] . L’intégration entre Domain et Infrastructure indépendamment du côté Application [côté Infrastructure]
  • @Fred Ce qui est compliqué à saisir (plusieurs de mes amis butent sur la même chose que vous), c'est que l'assemblage des différents morceaux (nommés "Application", "Domain" et "Infrastructure") dans la méthode "Main" (donc au plus haut niveau de mon programme) et n'induit pas une dépendance du morceau "Application" vers PoetryLibraryFileAdapter ou PoetryReader. La seule dépendance du morceau "Application" (ConsoleAdapter) c'est un IRequestVerses. Ce qui peut prêter à confusion c'est le nom choisi "Application" pour un des morceaux. Remplacez le par "Driver"(conducteur) si cela vous aide à séparer les choses Ce qui n'est pas expliqué dans la vidéo d'Alistair (ou ici dans ce post), c'est qu'il faut tout de même une "logique d'assemblage", c'est à dire un choix d'implémentations concrètes au moment où on démarre le programme dans son ensemble. Ici c'est "direct"... mais on pourrait imaginer une factory d'assemblage, qui change de choix d'infrastructure dans tel ou tel cas. Mon programme dépend des morceaux "Infrastructures" à sa disposition et connus de lui, oui. Mon morceau "Driver/Application" lui, ne voit pas, ne connait pas ces morceaux. On lui en donne un, et... du moment que ça rentre dans le moule IRequestVerses, ça lui convient. Par exemple: Un "site web Symfony"(PHP), appellera un morceau "Driver/Application" (un bundle) avec un morceau "Infrastructure"(PoetryLibrarySqlAdapter) et laissera la construction à un conteneur de service au démarrage de chaque requête HTTP La dépendance que vous notez pourrait se résumer ainsi: * Le "bootstrapper"de mon programme doit pouvoir choisir quelle implémentation concrète utiliser pour les 3 morceaux "Driver/Application", "Domain", "Infrastructure"au moment où il assemble ces morceaux pour créer une version viable de mon programme. La différence est subtile. En espérant que cette explication aide à y voir "un peu" plus clair.
  • Merci et bravo pour cet article très clair. Je me pose toutefois quelques questions sur certains détails (peut-être à la limite avec DDD) qui sont peu abordés dans la littérature. Pour rester sur notre app de poésie : Par exemple la pagination : je souhaite récupérer la liste des poèmes disponibles (TOUS les poèmes du monde), comment modéliser la pagination ? J'introduis la notion de page dans le domaine ? Idem sur les contrôles d'accès, est-ce que ça fait partie du domaine ? Un enfant ne peut lire/lister que certains les poèmes white-listés, comment intégrerais-tu cette contrainte, à quel niveau ?
  • Merci pour cet explication.J'ai une question: Une platforme BPMN sera membre de l'infrastructure ou bien membre du domaine?
  • Merci Fleury, j'ai essayé de corriger ça avec des tournures de phrases plus claires.
  • Merci thuvvik, effectivement, c'est ce que j'ai essayé de montrer dans la section Détail : Au Runtime
  • Bonjour Jo, pour la pagination effectivement le pattern architecture hexagonale nous laisse assez libre. Si on va vers DDD, j'ai trouvé cet article de @thinkb4coding sur le sujet intéressant : https://thinkbeforecoding.com/post/2009/04/08/Back-on-Repositories-and-Paging-Introducing-reporting Pour le contrôle d'accès, je ne sais pas répondre dans ce contexte :)
  • Bonjour Ahmed, je ne connais pas les platformes BPMN, donc je ne sais pas répondre.
  • Bonjour, excellent article qui m'a permis de découvrir l'architecture hexagonale. Merci. Arrive maintenant le temps de la première mise en pratique et des questions. A qui reviens la responsabilité de la mise en place des adapteurs ? Est-ce au "client" qui utilise l'Hexagone ou est-ce au développeur qui met en place la solution ? Merci.
  • Bonjour Flo, les adaptateurs sont du code à la frontière et en dehors de la couche métier. On va donc trouver des adaptateurs côté Application (User Side) et côté Infrastructure (Server Side), dont le rôle est de passer des objets métier à l'hexagone ou de traduire des objets métier en formats utilisables par le client. Typiquement on va trouver comme adaptateurs côté application des traductions vers d'autres objets de la couche application si c'est nécessaire, ou des serializers / deserializers en json, xml. Et côté infrastructure, des DTOs, des ORMs, ou des serializers / deserializers vers de la persistance. Ces formats et autres ORMs ne rentrent jamais dans la couche métier (principe "les dépendances vont vers l'intérieur"). En général, j'essaie la conception la plus simple possible, donc des traductions les plus légères possibles. On a déjà trois couches conceptuelles, si j'ajoute beaucoup de couches dans la partie Application ou Infrastructure, je me pose la question de savoir pourquoi j'ai besoin de ces couches supplémentaires. Je sors un peu de l'architecture hexagonale, mais il y a de bonnes discussions à ce sujet dans Patterns of Enterprise Application Architecture, Domain-Driven Design (le blue book), et Implementing Domain-Driven Design (le red book). (M.A.J. : j’ai mis l’article à jour en gardant les mots originaux, « User-Side », « Business Logic » et « Server-Side ». Se limiter au vocabulaire du contexte architecture hexagonale évite les ambiguïtés dont nous avons parlé.)
  • Bonjour, Merci pour ta réponse. Pour être sûr de ma compréhension, je prends le code source en exemple. Pourrait-on imaginer que la partie ConsoleAdapter (adapter de la couche application) pourrait être créée par une équipe différente de celle qui a crée l'hexagone afin de "consommer" le domain ? Merci
  • Bonjour Sébastien, tout d'abord, merci pour cet excellent article qui m'aide beaucoup dans mon apprentissage de l'architecture hexagonale. Je me posais une question au sujet de l'utilité de l'interface IRequestVerses. Aurais-tu un exemple où l'absence de cette interface poserait problème ? Pour le moment, une utilité que je trouve serait de diminuer la dépendance de l'application vers le domain. Donc si on modifie le domain, l'application sera plus facilement épargnée. Si on considère que la classe Poetry contient les attributs "title" et "content". J'imagine que l'application attend une liste de "title"/"content" et non un objet Poetry (dans le cas où l'application veut afficher la liste de tous les poèmes par exemple). Du côté FileAdapter, on renvoie des objets Poetry. Considérons maintenant que le client commande une nouvelle fonctionnalité qui nécessite l'ajout de l'attribut "autor". Dans le cas de l'utilisation, côté application, de DataTable.js, si on convertit directement l'objet en json, alors un champ est ajouté et la datatable plante. J'en conclue que l'utilisation d'une interface telle que IRequestVerses force le PoetryReader à traduire l'objet Poetry en un format de donnée précis attendu par l'application. Est-ce que mon raisonnement est juste ou est-ce que je me plante totalement ? Merci :)
  • Merci Sébastien pour cet article limpide, ça m'a beaucoup aidé à comprendre l'architecture hexagonale. Le site d'Alistair Cockburn est en construction donc pour ceux qui souhaitent lire l'article original de 2005, il est archivé ici : https://web.archive.org/web/20090223212534/http://alistair.cockburn.us/Hexagonal+architecture En 2012, Robert Martin a aussi écrit un excellent article sur l'architecture hexagonale qui souligne les principes importants et fournit un beau schéma : https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html En revanche je constate une erreur importante dans ton article Sébastien : la couche Application (appelée « Application Business Rules » dans le diagramme de Robert Martin) doit contenir les ports et les cas d'usage des entités de la couche Domaine du dessous (appelée « Enterprise Business Rules » dans le diagramme de Robert Martin), mais elle ne doit pas contenir les adaptateurs puisqu'ils ne font pas partie de l'application, ils sont à l'extérieur, cf. ce schéma de l'article d'Alistair Cockburn : https://web.archive.org/web/20071009083649/http://alistair.cockburn.us/index.php/Image:Image176.gif C'est la couche Adaptateur (appelée « Interface Adapters » dans le diagramme de Robert Martin) qui contient les adaptateurs. Pour information, Alistair Cockburn précise dans un autre article que la séparation entre la couche application et la couche domaine est facultative : https://wiki.c2.com/?HexagonalArchitecture Autrement dit, l'architecture hexagonale avec comme exemple 2 adaptateurs, ça donne ça (les flèches représentent les dépendances statiques) : user adapter -> [application -> domain] <- data adapter
  • Bonjour maggyero, merci, je suis content d'avoir aidé :)
    Et effectivement, il y a une ambiguïté dans mon article sur les mot "Application" et "Domain" : j'ai décidé d'appeler "Application" dans l'article ce qu'Alistair Cockburn appelle "User Side", et "Domain" ce qu'il appelle "Business Logic".
    Si on devait faire une analogie avec la Clean Architecture, ce "User Side" correspondrait à la moitié gauche des deux couches que Robert Martin appelle "Interface Adapters" et "Frameworks & Drivers". L'autre moitié (droite) de ces deux couches serait ce que j'appelle dans l'article "Infrastructure", et qu'Alistair Cockburn appelle "Server Side".
    Quand à ce que j'appelle "Domain" dans l'article, et qu'Alistair Cockburn appelle "Business Logic", ça correspond à peu près à ce que Bob Martin appelle "Business Rules" (les deux couches "Application Business Rules" et "Entreprise Business Rules"). Mais ici l'analogie est plus floue. Donc je n'aime pas trop essayer de faire correspondre archi hexa et clean archi, elles ne se recouvrent pas exactement.
    Ce que je fais depuis quelques temps pour éviter ces ambiguïtés, j'utilise soit archi hexa, soit clean archi (jamais les deux) et je me cantonne strictement au vocabulaire et aux patterns de l'archi que j'ai choisie :
    • Pour discuter d'archi hexagonale, je n'utilise que les mots "User Side", "Business Logic" et "Server Side".
    • Pour discuter de clean archi, je n'utilise que les noms des couches de la Clean archi. Par exemple je ne parle pas de "Domain" mais de "Business Rules".
    En gros, depuis quelques temps je me limite aux mots du contexte dans lequel je me situe ("context is king"), ça simplifie beaucoup les discussions :) Et merci également pour les liens :)
    (M.A.J. : j'ai mis l'article à jour en gardant les mots originaux, "User-Side", "Business Logic" et "Server-Side". Se limiter au vocabulaire du contexte architecture hexagonale évite les ambiguïtés dont nous avons parlé.)
  • Salut Sébastien, merci pour ta réponse. Après avoir discuté avec Alistair Cockburn, celui-ci m'a recommandé un article récent plus détaillé de Juan Manuel Garrido de Paz sur l'architecture hexagonale : https://softwarecampament.wordpress.com/portsadapters/ Ce que tu appelles « application » dans l'article est ce qu'Alistair appelle un adapteur primaire (primary adapter ou driver adapter ou user-side adapter). Ça peut correspondre à un client graphique, un client en ligne de commande, un consommateur de file d'attente, un serveur web, etc. En général on nomme plutôt « application » le cœur du logiciel (la partie fonctionnelle, le métier), accessible depuis l'extérieur par son interface de programmation, c'est-à-dire son API, qu'Alistair appelle les ports de l'application. Nommer « application » l'interface utilisateur n'est pas commun à mon sens. Par ailleurs ce que tu appelles « application » fait aussi partie de l'infrastructure, puisque ça peut aussi être un serveur web ou un consommateur de file d'attente. En fait tout ce qui est entre l'hexagone et les acteurs externes (humains, base de données, réseau, système de fichiers, etc.) est de l'infrastructure (les adapteurs). Donc ta séparation application/infrastructure prête aussi à confusion je pense. Même si Alistair dit bien qu'il existe une asymétrie entre le côté gauche et le côté droit de l'hexagone, ce n'est pas l'asymétrie infrastructure/superstructure dont tu parles, qui elle est une autre asymétrie qui existe entre l'intérieur (la superstructure, la fonctionnalité) et l'extérieur (l'infrastructure, la technologie) de l'hexagone (pas entre son côté gauche et son côté droit). L'asymétrie entre le côté gauche et le côté droit dont parle Alistair se passe au niveau du flux de contrôle : le côté gauche contrôle l'interaction tandis que le côté droit est contrôlé par l'interaction. Juan Manuel Garrido de Paz consacre d'ailleurs une section de son article aux symétries et asymétries de l'architecture hexagonale. Il existe également deux symétries : les côtés gauche et droit dépendent statiquement de l'hexagone, et l'hexagone dépend dynamiquement des côtés gauche et droit. En résumé, l'architecture hexagonale possède : 1 asymétrie intérieur/extérieur (superstructure/infrastructure), 1 asymétrie gauche/droite (contrôleur/contrôlé) et 2 symétries gauche/droite (dépendances statiques identiques et dépendances dynamiques identiques). J'ai également montré à Alistair l'article sur l'architecture Clean de Robert Martin et, bien que n'étant pas familier de son architecture, Alistair ne comprend pas pourquoi Robert nomme sa couche la plus externe « Frameworks & Driver » puisque c'est la couche où sont censés se trouver les acteurs (humains, base de données, réseau, système de fichiers, etc.). Il dit également que la division à la domain-driven design de la couche application de l'architecture hexagonale en 2 sous-couches (une sous-couche « application » — aussi appelée couche service — que Robert appelle « Application Business Rules » et à l'intérieur une sous-couche domaine — aussi appelée couche métier — que Robert appelle « Enterprise Business Rules ») n'est pas une contrainte de l'architecture hexagonale. L'architecture hexagonale reste muette sur le contenu de l'application (ça peut être du DDD service/domaine, ça peut être un plat de spaghetti, peu importe).
  • Super, merci ! Je pense qu'on dit la même chose (voir les notions de "qui pilote" et "qui est piloté" dans l'article, et sur le fait qu'à l'intérieur de l'hexagone on est libre d'organiser les choses comme on le souhaite), et que :
    • Je n'aurais pas dû nommer "Application" le User Side (qui pilote, qui drive)
    • Je n'aurais pas dû nommer "Infrastructure" le Server Side (qui est piloté, driven)
    • Je n'aurais pas dû nommer "Domain" la Business Logic (l'hexagone)
    En bref, j'aurais du utiliser les mots de l'article original pour les trois zones :) Ça cause une confusion inutile. D'où la position que j'ai adoptée (après l'écriture de l'article) et que je recommande : pour enseigner, n'utiliser que les mots de l'archi adoptée, et leur définition dans ce contexte précis. Ensuite, si on veut comparer l'archi en couche, l'archi hexagonale, la clean archi, c'est très intéressant mais à mon avis ça mérite un article en soi vu les différences subtiles de vocabulaire et de concepts.
  • Merci infiniment monsieur Sébastien.
  • Bravo pour ce super article !
  • Bonjour, quelle est la différence avec une architecture en couches (ex. 3 tiers, mais cela pourrait être plus ou moins), chacune étant la plus indépendante l'une des autres? Sachant que l'indépendance des couches est la base même de cette architecture en couches, et donc l'utilisation d'interfaces (de quelque type que ce soit, pas forcément interfaces de POO d'ailleurs), et une condition nécessaire de sa mise en place.
  • Bonjour, je vois deux différence comme ça. Un, l'architecture en couche "en général" est sur n couches. Et deux, sur l'architecture en trois niveaux en particulier, la différence principale est l'inversion de dépendance entre couche infra (ou server side) et couche domaine (ou business logic). En gros, si les flèches représentent "dépend de..." en architecture à trois niveaux classique on a : application -> domaine -> infrastructure Alors qu'en architecture hexagonale on a : user side -> business logic <- server side. Je ne sais pas, mais j'émets l'hypothèse non vérifiée que : avec la popularisation de l'inversion de dépendance, la plupart des architectures à trois niveaux modernes (= post ~2005 ?) sont en fait des architectures hexagonales qui s'ignorent ?
  • Bravo et merci pour cet article, un gros boulot pour la communauté qui nous fait bien progresser.... Thanks 👍🏻
  • Super article, cependant je me pose une question. Quelque chose doit m’échapper. Imaginons que j'ai Une partie application qui est une simple API Une partie domain qui gere un CRUD simple Une partie driven qui gere la persistance des données Et autre partie driven qui gere l'ecriture de log (fichier ou autre). Chacune de ces couches est susceptibles de créer des erreurs. Si je veux créer garder un journal de log de ces erreurs je le fais via un port/adaptateur entre le domain et la partie driven ? Si le domain produit une erreur : pas de problème pour logger mon erreur.. je passe par le port ILogger qui se charger de logger tout ca via l’implémentation faite dans la partie driven. Par contre s'il de produit dans un une implémentation d'un autre port (persistance) comment acceder a mon implémentation de mon logger ? Idem si une erreur se produit dans la partie API, : dois je passer par le domain pour atteindre mon implémentation du logger ? Mon logger doit pouvoir etre utilisé dans l'application, le domain et l'infra.. comment faire ? faut il l’implémenter 3 fois ?
  • Bonjour Sébastien J'en suis à la moitié de l'article et pour le moment je comprends tout contrairement à d'autres que j'ai été amener à lire. Je comprends mieux le principe et la force de cette archi Merci Par contre n'y a t-il pas un abus de langage concernant l'héritage ? : "le fileAdapter est aussi une instance de IObtainPoems par héritage." Ne faudrait-il pas parler de polymorphisme ?
  • C'est toujours un grand débat que j'ai avec les puristes: ne considérez vous pas que les annotations ne constituent pas un pattern d'architecture hexagonale? Perso, je n'ai aucun souci à mettre un @Transcational @Service, @Entity ou un @Inject dans mes services et objets métiers!
  • Superbe article bravo ! @Mika je pense que la réponse à ta question est que c'est géré grace à l'IOC
  • Très bon article qui rends le concept très clair. ça donne envie de tester
    1. Laisser un commentaire

      Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *


      Ce formulaire est protégé par Google Recaptcha