D’une architecture en couche classique vers une architecture hexagonale avec Spring boot

Fabrice Rabarijaona
6 min readJul 7, 2020

--

Hexagon (Credits vectorstock.com)

Cela fait maintenant quatre ans que je pousse à utiliser l’architecture hexagonale pour structurer les projets sur lesquels j’ai eu à intervenir lors de mes différentes missions en tant que développeur Java freelance.

Je tiens à rappeler que ce n’est pas une architecture qui s’applique systématiquement mais qui peut être intéressante dans le cas où le projet a un domaine métier riche et où il est important de pérenniser la logique métier dans le temps.

En pré-requis de la lecture, vous devez être un développeur, un tech lead ou un architecte qui connaît Spring et qui souhaite mettre en place l’architecture hexagonale sur votre projet en Spring boot.

Cet article se limite à l’implémentation telle qu’Alistair Cockburn l’a défini en 2005. Vous ne retrouverez donc pas des notions comme DDD, CQRS, CQS, etc.

On va partir d’un use case sommaire d’un système de panier d’un site e-commerce qui permet d’ajouter un produit dans un panier et de récupérer le contenu du panier.
Avec les règles fonctionnelles suivantes :
- La quantité pour chaque produit ajouté dans le panier est recalculée en fonction de la disponibilité en stock du produit
- Une remise de 10% est appliquée si le montant du panier dépasse 100 €

Les informations liées aux produits sont fournies par un système distant.
La quantité disponible en stock pour chaque produit est fournie par un autre système distant.

Pour expliquer ce qu’est l’architecture hexagonale, on va partir d’une architecture en couche classique.

Si vous utilisez Spring au quotidien, vous connaissez sûrement les stéréotypes @Controller, @Service, @Repository qui permettent de déclarer une classe en tant que bean dans le cas d’utilisation du mécanisme d’auto-configuration.

Dans une architecture en couche:
- les classes annotées avec @Controller sont les points d’entrée de votre application qui peuvent exposer soit un IHM, soit un API REST, etc.
- les classes annotées avec @Service vont implémenter les règles métiers, une classe service pouvant se référer à une autre.
- les classes annotées avec @Repository vont implémenter les accès à la solution de persistance.

Dans une architecture en couche, les contrôleurs dépendent des services, les services dépendent eux-mêmes d’autres services ou de repositories (Voir le schéma ci-dessous).

Architecture en couche classique.

Je ne vais pas rentrer dans le détail d’implémentation, mais vous avez déjà sûrement rencontré ce type d’architecture dans les différents projets sur lesquels vous avez travaillé.

Un des principes de l’architecture hexagonale est que le domaine qui contient les implémentations des règles métiers ne doit pas avoir de dépendance technique (type framework) et ne doit pas dépendre de l’extérieur de l’hexagonale.

Un deuxième principe à retenir : les dépendances doivent se faire de l’extérieur de l’hexagone vers l’intérieur et jamais l’inverse.

L’hexagone est séparé en deux parties, une partie gauche et une partie droite :
- la partie gauche (API) permet de faire des appels vers le domaine, c’est généralement le point d’entrée du domaine.
- la partie droite (Service Provider Interface, SPI) permet au domaine de récupérer (resp. envoyer) des informations ou des données provenant de (resp. vers) l’extérieur du domaine et nécessaires à l’implémentation des règles métiers.

Un schéma vaut mieux qu’un long discours :

Architecture hexagonale bird view

La dépendance de l’API vers le domaine est assez facile à mettre en oeuvre, mais la dépendance des SPIs vers le domaine peut être déroutante.
L’API et les SPIs sont définies en tant qu’interface. L’implémentation de ces interfaces vont se retrouver à l’extérieur de l’hexagone.

Si on revient à notre use case initial :

Hexagonal architecture: domain

L’interface CartApi est le point d’entrée de notre hexagone (partie gauche).

Les interfaces CartPersistenceSpi, ProductSpi et StockSpi sont les interfaces qui définissent le contrat que doivent implémenter les dépendances de notre hexagone (partie droite).

La classe CartService implémente les règles métiers spécifiques à notre domaine.

A cette étape de notre implémentation, on a une application fonctionnelle qui est testable et qui n’a aucune connaissance ni de comment elle va être exposée, ni comment la persistance des données va être implémentée techniquement, ni de quelle manière les informations des dépendances vont être récupérées. Le code écrit est du pur code métier.

Si vous voulez voir l’implémentation du projet avec l’état du code à cette étape vous pouvez allez sur mon repo github : https://github.com/rabarijaona/hexagonal-arch et faire un checkout de la branche step1-init-domain.

A l’issue de cette première implémentation, on peut constater que le domaine est écrit avec du pure langage Java et ne dépend d’aucun framework.

Pour vous assurer que la règle d’absence de dépendances techniques dans le code métier et les règles du sens des dépendances soient toujours respectées , vous pouvez utiliser le plugin enforcer de maven ou la librairie archunit qui permet d’écrire des tests unitaires permettant de valider la structuration de votre projet.

L’étape suivante consiste à implémenter les différentes interfaces API et SPIs définies au niveau du modèle.

Dans notre implémentation finale:
- la classe CartController sera celle qui va exposer notre domaine via un API REST.
- l’implémentation respective des SPIs ProductClientService et StockClientService utilisera une instance de WebClient pour faire appel au service distant, la classe PersistenceService aura une dépendance à une classe Repository qui sera l’implémentation concrète pour accéder au système de persistance.

Si vous voulez voir l’implémentation du projet avec l’état du code à cette étape vous pouvez allez sur mon repo github : https://github.com/rabarijaona/hexagonal-arch et faire un checkout de la branche step2-infra.

Hexagonal architecture — full implementation

Comme vous pouvez le voir dans le diagramme ci-dessus, le sens des dépendances va toujours de l’extérieur vers l’intérieur du domaine.

Pour terminer, je fais un rappel des avantages que peut offrir la mise en place de l’architecture hexagonale sur un projet :
- Il y a tout d’abord la testabilité de l’application. On peut par exemple faire du Behavioral Driven Development pour tester l’API de l’hexagone. Et on peut respecter la pyramide des tests en concentrant les tests métiers au niveau des tests de l’API.
- Le fait de pouvoir décaler les choix des technologies à utiliser. On a pu voir que dans le step 1, on a pu implémenter les règles métiers sans aucune dépendance à un framework.
- La possibilité de switcher de technologies facilement ou même de faire cohabiter deux technologies en même temps pour l’exposition du service métier.
- La facilité d’intégration d’un nouveau développeur sur le projet dû à la structuration et à l’organisation du projet.

En contrepartie de ces avantages, l’architecture hexagonale rajoute de la complexité : on a pu voir que par rapport à une architecture en couche qui est linéaire, on a un deuxième niveau d’indirection et on a rajouté des mappings à chaque fois que l’on veut passer de l’intérieur vers l’extérieur de l’hexagone et réciproquement.

Comme je l’ai mentionné en début de cet article, l’architecture hexagonale n’est pas adaptée pour tout type de projet et peut être overkill dans certains cas.

Il est essentiel de bien identifier si le projet à mettre en place a un domaine métier riche, c’est là que l’architecture hexagonale pourra montrer toute sa puissance et son intérêt.

--

--

Fabrice Rabarijaona

I am a freelance developer who is passionate about craftmanship, software architecture.