ArchUnit & Architecture Hexagonale : Garantir la pérennité de votre architecture

Garder une architecture propre… même quand le réel s’en mêle

On s’est tous déjà retrouvés dans un projet qui au départ est limpide, où tout est clair : responsabilités clairement définies et architecture impeccable.

Cependant, la réalité du projet s’impose rapidement : les délais se raccourcissent, les équipes évoluent, et les connaissances s’érodent malgré nous, menant à des compromis précipités. Et c’est là que rien ne va plus, on finit par se retrouver avec un contrôleur qui appelle directement un repository pour éviter un DTO. Une classe métier importe « temporairement » une bibliothèque d’infrastructure, et voilà qu’il en est fini de notre belle architecture.

Une à une, ces décisions ne sont pas catastrophiques. Mais collectivement, c’est toute l’architecture qui meurt à petit feu. Le temps passe et les frontières se brouillent. Alors, on ne se rend pas compte mais modifier une brique est devenu extrêmement risqué puisqu’une modification d’une « petite pierre » entraîne un effet domino et toutes les fondations s’écroulent.

Il ne s’agit pas vraiment d’une faute de compétences, ni de discipline des développeurs. C’est que l’architecture n’est jamais que ce concept abstrait qui est rarement vérifié de manière formelle ou automatique.

Pourquoi tester son architecture

Les tests unitaires servent à valider le comportement fonctionnel d’un système.
Les tests d’architecture permettent de valider ses propriétés structurelles.

ArchUnit est une bibliothèque Java qui propose d’exprimer des contraintes architecturales sous forme de règles exécutables dans le cycle de test standard et qui échouent si ces règles ne sont pas respectées.
👉 Si une classe du package domain dépend d’une classe du package infrastructurele test échoue.

Aucune architecture ne reste spontanément pure. L’expérience montre qu’il est inévitable que des dépendances « parasites » apparaissent naturellement dans tous les projets d’une certaine taille. ArchUnit propose un langage déclaratif pour formaliser des règles de dépendances et les exécuter comme des tests standards.

💻 Exemple :

👉 Il s’agit d’une règle simple, mais fondamentale : il n’y a pas de dépendance entre le cœur métier et le technique.

Lorsque l’on introduit une dépendance qui va contre cette règle, le test échoue, signalant une dérive avant qu’elle ne se propage.

➡️ Ces tests constituent l’ultime filet de sécurité conceptuel.

ArchUnit n’analyse pas le fichier .java, il se penche plutôt sur le bytecode .class. C’est-à-dire sur la version compilée du projet.

Conséquence : il est donc librement indépendant de l’IDE, du style de codage ou des commentaires, il voit ce que la JVM voit.

 

Architecture hexagonale : du concept à la vérification systématique

L’architecture hexagonale, également désignée sous l’appellation de modèle du type Ports & Adapters, permet de séparer sans ambiguïté l’implémentation de la solution métier de son intégration technique et repose sur une philosophie de dépendances orientée vers le domaine.

  • Le domaine regroupe exclusivement la logique métier à l’état pur, indépendante de la contrainte imposée par les frameworks.
  • Les ports spécifient les points d’interaction abstraits entre le domaine et les autres couches.
  • Les adaptateurs, quant à eux, spécifient des implémentations techniques des ports pour gérer les détails d’intégration (JPA, REST, files, etc.), chacun d’eux dans le respect des contraintes et du comportement des ports concernés.

Une telle architecture rend le logiciel testable, modifiable, extensible et compréhensible. ArchUnit vient appuyer cette maîtrise technique en vérifiant la conformité du code avec cette hiérarchie. Ainsi, aucune dépendance ne doit remplir d’injection de dépendances venant du domaine vers l’infrastructure ou les frameworks externes.

Au-delà de son aspect purement graphique, il s’agit bien là d’un engagement qui garantit les flux de données et de responsabilités unidirectionnels. L’architecture hexagonale, au-delà du modèle purement mental, devient ici vérifiable au travers d’un contrat à respecter.

 

Du principe à la pratique quotidienne

Pour illustrer clairement notre propos concernant ArchUnit, prenons un cas typique où un développeur aurait rajouté par inadvertance une dépendance Infrastructure vers Domaine. Un service métier a par exemple commencé à appeler directement un JpaRepository pour ne pas avoir à écrire une requête. Sans garde-fou, cette erreur passe inaperçue.

Grâce à ArchUnit, la règle définie précédemment remonte instantanément une alerte : le test échoue, indiquant qu’une classe du domaine dépend d’un élément d’infrastructure. La correction est alors explicite : sortir l’appel dans un adaptateur et injecter le port dans le service métier.

L’un des points forts d’ArchUnit réside dans la lisibilité de son DSL (Domain-Specific Language), conçu pour être lisible naturellement (human readable). Chaque règle s’exprime comme une phrase claire et fluide, proche de la logique de la programmation fonctionnelle : on compose des conditions et des prédicats avec des méthodes enchaînées (that,should,resideInAPackage), rendant la lecture naturelle même pour un œil extérieur. Cette approche favorise la compréhension immédiate des intentions architecturales.

ArchUnit se marie parfaitement avec la chaîne de développement continu. Au-delà du simple contrôle statique, il devient un outil de documentation vivante et de prévention collective : les tests ArchUnit sont intégrés au cycle standard de validation des livrables, à égalité avec les tests unitaires et d’intégration.

 

Voici par exemples quelques règles simples et utiles :

  • Interdire les dépendances du domaine vers les packages springframework.*
  • S’assurer que les controllers vivent uniquement dans ..web..
  • Empêcher les services applicatifs d’interroger directement le domaine.

En plus des règles d’interdiction simples, ArchUnit propose aussi des règles plus avancées : interdiction de dépendance circulaire, validation du nom des packages, ou encore vérification de la cohérence entre la structure physique et structure logicielle de l’application. Ces règles permettent d’évaluer en continu l’évolution structurelle du projet.

 

Exemple de règle personnalisée : il est aussi possible de toujours s’assurer qu’un ensemble de classes d’un package donné suit une convention de nommage, par exemple en s’assurant qu’elles se terminent toutes par UseCase. Cela permet de garantir une cohérence de conception dans les différentes couches applicatives :

Cette règle, simple mais efficace, garantit que la convention de nommage est appliquée uniformément, ce qui facilite la lecture du code et permet d’identifier immédiatement le rôle des classes.

Intégration progressive dans un projet existant

L’utilisation d’ArchUnit ne requiert pas de refonte. On peut y recourir progressivement sans lourdeurs :

  1. Ajout de la dépendance dans le module de test :
  2. Création d’un dossier à cet effet : src/test/java/.../architecture pour centraliser la règle.
  3. Rédaction de règles essentielles (dépendances entre domaines, nommage, couches) puis enrichissement progressif.
  4. Exécution dans la CI pour garantir la cohérence architecturale à chaque merge ou release.
  5. Partage de modèles et bonnes pratiques au sein de l’équipe ou de la communauté interne.

 

Ce processus, une fois institutionnalisé, transforme la validation architecturale en habitude collective, non en contrainte ponctuelle. Chaque nouveau développeur bénéficie ainsi d’un cadre de référence clair.

Un levier pour le craft et la transmission

L’architecture logicielle n’est pas figée : elle évolue au rythme de l’organisation et des besoins. ArchUnit soutient cette dynamique en instaurant une forme de contrat social autour du code. Les règles d’architecture deviennent explicites, discutables, révisables, et leur non-respect est immédiatement détecté.

Dans une démarche craft, cette pratique constitue une garantie essentielle : maintenir un code non seulement fonctionnel, mais aussi compréhensible et durable. Les tests d’architecture deviennent alors une manifestation tangible de la culture de qualité d’une équipe.

ArchUnit ne remplace ni la réflexion ni la conception : il les prolonge. Il agit comme un garant silencieux de la cohérence du projet, rappelant que chaque dépendance est un choix, et qu’un bon choix aujourd’hui évite des migrations coûteuses demain.

Conclusion

Une architecture saine ne repose pas uniquement sur la conception initiale, mais sur la capacité à en vérifier continuellement la stabilité. ArchUnit, allié à une approche hexagonale rigoureuse, permet de concilier innovation, évolutivité et robustesse.

En intégrant la validation structurelle dans le cycle de vie du code, une équipe institutionnalise sa culture de qualité. Elle réduit les effets de dérive, renforce la compréhension collective et transforme la maintenance en activité maîtrisée.

Article rédigé par Thomas MAURIER