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.
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 ?
💡 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 :
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 :
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 :
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. 🙂
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 :
@Configuration
et @Bean
pour indiquer à Spring de gérer manuellement l’injection de WalletRepositoryPort
dans le constructeur de DepositMoneyUseCase
🚫 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 🙂.
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 :
@DomainUseCase
.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
:"io.dfrances.multi.module.hexagonal.api.domain"
pour y trouver des classes.@DomainUseCase
.type = FilterType.ANNOTATION
indique que Spring doit chercher des annotations spécifiques.classes = DomainUseCase.class
précise notre annotation cible.🤖 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 à :
@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 :
@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)
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. 😉
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.