8
minutes
Mis à jour le
15/5/2025


Share this post

Découvrez comment découpler votre domaine métier de Spring Boot dans une architecture hexagonale. Cet article vous guide pas à pas vers une approche propre et scalable en utilisant des annotations custom Java, pour une meilleure testabilité et indépendance du code.

#
Java
#
Spring Boot
#
HexagonalArchitecture
#
Backend
Dorian Frances
Software Engineer

Le problème que l’on veut résoudre

Lorsque l’on développe une API avec Spring Boot, il est fréquent d’utiliser les annotations fournies par le framework, telles que @Service, @Component ou @Repository pour réaliser nos injections de dépendances. Si cette approche fonctionne parfaitement dans le cadre d’architecture plus “traditionnelles”, elle peut poser problème dans le cadre de l’implémentation d’une architecture hexagonale.

Contexte : développer une API avec l’architecture hexagonale

L’architecture hexagonale repose sur un principe central : l’indépendance du domaine. Cela signifie que la partie “métier” (ou Domain) de notre application doit se prémunir de toute dépendance envers des frameworks externes comme Spring Boot, Hibernate ou tout autre implémentation technique. Pourquoi ? Car le domaine représente la logique fondamentale de notre application, qui doit pouvoir évoluer indépendamment des différents outils ou infrastructure que l’on utilise.

En quoi cela peut être utile ?

  • Nous pouvons commencer à développer le domaine avant d’acter sur nos choix techniques.
  • Il est bien plus facile de tester le domaine sans se soucier du framework choisi.

💡 Pour celles et ceux qui ne seraient pas familier avec les concepts d’architecture hexagonale et de Domain-Driven-Design, je vous mets ici quelques ressources qui pourraient vous intéresser :

Le couplage avec Spring Boot : comprendre les implications

L'utilisation des annotations Spring Boot comme @Service ou @Component dans notre domaine métier crée un couplage technique qu'il convient d'examiner attentivement. Cette dépendance a des implications importantes :

  • Elle lie notre logique métier à un framework spécifique, ce qui peut compliquer les migrations futures.
  • Elle peut rendre plus difficile l'extraction ou la réutilisation du code métier dans d'autres contextes.
  • Sur des applications destinées à durer dans le temps, ce couplage risque de transformer notre code en legacy difficile à faire évoluer pour les équipes futures.

Cependant, l'utilisation des annotations Spring présente aussi des avantages immédiats en termes de simplicité et de conventions établies.

Face à cette situation, deux approches s'offrent à nous :

  1. Accepter ce couplage si nous sommes certains que notre application restera toujours liée à Spring Boot.
  2. Rechercher des alternatives permettant d'isoler notre domaine métier du framework technique.

Ce choix dépend de la question fondamentale : "Est-ce que j'accepte que mes règles métier dépendent d'un framework technique ?"

Si votre réponse est non, il existe des solutions élégantes pour éviter ce couplage, et c'est justement l'objet de cet article : vous présenter une approche alternative utilisant des annotations personnalisées en Java standard plutôt que des annotations spécifiques à Spring Boot.

Défi : réaliser les injections de dépendances sans dépendre de Spring Boot

Une des fonctionnalités majeures de Spring Boot est la gestion automatique des injections de dépendances via son conteneur IoC. Si nous ne voulons pas que le domaine soit lié à Spring, comment pouvons nous quand même bénéficier de ces injections “automatique” ? Trois solutions s’offrent à nous :

  1. Faire les injections “à la main” via des classes de configuration et en définissant les beans nécessaires.
  2. Identifier les classes du domaine à intégrer au conteneur IoC via le nom de classe.
  3. Utiliser des annotations java custom pour indiquer à Spring Boot quelles classes du domaine doivent être intégrées au conteneur IoC (cette méthode est semblable à la précédente mais elle est un poil plus robuste car elle ne dépend pas du nommage de nos classes).

Dans la suite de cet article, nous allons passer rapidement sur la première méthode et ses limites. Nous verrons ensuite l’implémentation de la troisième méthode qui va s’avérer bien plus simple et scalable. 🙂

L’injection de @Bean à la main

Considérons un cas concret dans lequel nous avons un usecase de notre domaine, DepositMoneyUseCase, qui dépend d’un port nommé WalletRepositoryPort. Voici comment nous pourrions configurer cette dépendance dans une classe dédiée :

  • Nous utilisons ici @Configuration et @Bean pour indiquer à Spring de gérer manuellement l’injection de WalletRepositoryPort dans le constructeur de DepositMoneyUseCase
  • Cette approche fonctionne très bien, car elle respecte l’indépendance du domaine. Aucun dépendance à Spring n’est introduite dans notre domaine comme nous pouvons le voir dans le bout de code suivant.

🚫 Les limites de cette méthode

Imaginons que nous ajoutons plusieurs nouveaux usecases ou ports à notre domaine. Chaque nouvelle classe ou dépendance nécessite une mise à jour manuelle de la configuration. Cela devient vite fastidieux, surtout dans des projets de grande envergure avec beaucoup de logique métier.

Bien que l’injection manuelle respecte les principes de l’architecture hexagonale, elle peut rapidement devenir un goulot d’étranglement en termes de productivité. Pour éviter ces inconvénients, une alternatives plus élégante consiste à utiliser des annotations custom, qui permettent de simplifier et d’automatiser la déclaration des beans tout en maintenant l’indépendance du domaine.

C’est la solution que je vais vous présenter dans la section qui suit 🙂.

L’utilisation d’annotation custom

Principe

L’idée centrale est de créer une annotation, par exemple @DomainUseCase, pour marquer les classes du domaine que Spring Boot doit gérer. Plutôt que d’annoter directement nos classes avec @Service ou @Component (ce qui introduirait une dépendance à Spring Boot), nous utilisons notre propre annotation, spécifique à notre projet.

Ensuite, une configuration Spring est utilisée pour :

  1. Scanner les classes annotées avec @DomainUseCase.
  2. Enregistrer automatiquement ces classes comme beans dans conteneur IoC de Spring.

Maintenant, transformons notre précédent exemple pour mettre en place cette méthode. Tout d’abord, nous allons “décorer” notre usecase métier avec notre nouvelle annotation custom :

Cette nouvelle annotation est créée dans le domaine et est définie comme suit :

Il nous reste à indiquer à Spring que les classes décorées par notre nouvelle annotation doivent être scannées pour qu’il les intègre dans son conteneur IoC, et ça se passe dans notre fameuse classe de configuration.

  • @ComponentScan :
    • Scanne le package "io.dfrances.multi.module.hexagonal.api.domain" pour y trouver des classes.
    • Applique un filtre spécifique pour inclure uniquement les classes annotées avec @DomainUseCase.
  • Filtre personnalisé :
    • type = FilterType.ANNOTATION indique que Spring doit chercher des annotations spécifiques.
    • classes = DomainUseCase.class précise notre annotation cible.

Les points forts de cette méthode :

🤖 Automatisation complète

Contrairement à l’injection manuelle, où chaque nouveau usecase doit être ajouté explicitement dans une classe de configuration, cette méthode détecte automatiquement les classes annotées @DomainUseCase. Ainsi, l’ajout d’un nouveau usecase se résume à :

  1. Écrire la classe.
  2. L’annoter de @DomainUseCase

Spring s’occupe du reste.

📏 En quoi est-ce que cela s’aligne avec l’architecture hexagonale ?

Cette méthode fait écho au principe d’inversion de dépendance :

  • Ce n’est plus le domaine qui dépend du framework (Spring Boot en l’occurence).
  • C’est Spring Boot qui s’adapte au domaine en identifiant les classes nécessaires grâce à l’annotation @DomainUseCase

Petit bonus : Documentation pour les développeurs

L’utilisation d’annotation custom, comme notre @DomainUseCase, peut améliorer la lisibilité du code et faciliter l’onboarding de nouveaux développeurs qui ne serait pas sensibiliser aux principes de l’architecture hexagonale.

Pour celles et ceux utilisant IntelliJ IDEA (je ne sais pas pour les autres), nous pouvons même mettre en forme cette documentation directement depuis l’IDE 🙂

NB : depuis Java 23 il est possible d’utiliser MarkDown pour formater la Javadoc (https://openjdk.org/jeps/467)

Conclusion

L’indépendance du domaine est un pilier fondamental de l’architecture hexagonale. En veillant à ce que notre logique métier ne soit pas dépendante d’un framework technique comme Spring Boot, nous rendons notre application plus testable et robuste au changement. J’entends déjà certains détracteurs me susurrer à l’oreille que ne pas utiliser les annotations Spring dans le domaine est quelque chose d’overkill. Comme nous l’avons vu à travers cet article, créer une annotation custom que l’on pourra réutiliser à de nombreux endroits de notre domaine n’est pas beaucoup plus coûteux !

Cela dit, tout dépend du contexte de notre projet. Si ce genre de concept nous semble inutilement complexe ou peu adapté, rien ne nous empêche de prendre certains concepts de l’architecture hexagonale et d’en délaisser certains. À vous de voir. 😉

BONUS : Que vaut le coup de migration si je veux migrer sur Quarkus (par exemple) ?

Quarkus repose sur l’extension Arc (CDI), et ne supporte ni @ComponentScan, ni @Configuration.

À la place, il détecte automatiquement les beans à condition qu’ils soient dans le classpath de compilation et annotés avec des annotations CDI (@Singleton, @ApplicationScoped, etc.).

Si l’on veut continuer à utiliser @DomainUseCase comme repère, nous pouvons la transformer en @Stereotype CDI :

De cette manière, nous pouvons retirer la classe de Configuration. Nous perdons, certes, l’indépendance du domaine car notre annotation est maintenant décorée de @Stereotype et @ApplicationScoped mais autrement, nous avons littéralement copié-collé notre domaine d’un projet à l’autre avec un coup de migration assez faible.