Frontière hexagonale en Kotlin : modules Gradle plutôt que des dossiers
L'article sur l'architecture hexagonale en pratique
le disait déjà : la question qui compte n'est pas « est-ce dans le bon
dossier » mais « le domaine compile-t-il seul si on supprime les
adapters ». Un dossier domain/ ne répond jamais vraiment à cette question,
il ne fait que suggérer une intention. La seule façon de la transformer en
garantie, c'est de la faire vérifier par le compilateur, pas par la
relecture de code.
Ce qu'un dossier ne peut pas empêcher#
Rien, dans un projet Gradle à module unique, n'empêche un fichier du dossier
domain/ d'importer org.springframework.stereotype.Component ou
jakarta.persistence.Entity. Le code compile, les tests passent, et la
frontière hexagonale existe uniquement dans la tête de qui a écrit le code
et de qui l'a relu. C'est une discipline, pas une contrainte : elle tient
tant que personne ne l'oublie sous la pression d'une deadline.
La frontière comme dépendance Gradle, pas comme convention de dossier#
Un module Gradle dédié au domaine, sans aucune dépendance vers Spring,
Jakarta ou un driver de base de données, transforme cette discipline en
contrainte de compilation. Si un fichier du module domain importe une
classe Spring, le build échoue avec une erreur de résolution, pas une
remarque en revue de code.
// settings.gradle.kts
include("domain", "application", "infrastructure", "presentation")// domain/build.gradle.kts
dependencies {
// rien d'autre que le langage lui-même
implementation(kotlin("stdlib"))
}// application/build.gradle.kts
dependencies {
implementation(project(":domain"))
// toujours aucune dépendance framework ici
}// infrastructure/build.gradle.kts
dependencies {
implementation(project(":domain"))
implementation(project(":application"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}Le graphe de dépendances que Gradle refuse de laisser inverser#
infrastructure -> application -> domain
presentation -> application
domain ne dépend jamais d'un module en amont : Gradle n'offre
littéralement pas de chemin pour déclarer l'inverse sans créer
un cycle, que le build rejette de lui-même.
Un import qui remonterait de domain vers infrastructure ne produit pas
une classe mal rangée, il produit un cycle de modules que Gradle refuse de
résoudre. La violation devient une erreur de build, plus tôt et plus
systématiquement qu'une remarque de revue de code qui dépend de la
vigilance de qui relit.
Ce que ça coûte#
Cette rigueur a un prix : plus de fichiers build.gradle.kts à maintenir,
un temps de configuration Gradle légèrement plus long, et une gymnastique
supplémentaire chaque fois qu'un type doit franchir une frontière de module
(des DTOs de mapping en plus, là où un module unique laissait parfois passer
une entité de domaine directement jusqu'au contrôleur). Sur un projet de
quelques semaines, ce coût dépasse largement le bénéfice. Sur un projet
fait pour vivre des années, avec plusieurs contributeurs qui n'auront pas
tous lu la même documentation d'architecture, faire vérifier la frontière
par le compilateur plutôt que par la mémoire collective de l'équipe devient
rentable dès la première fois où ça évite une régression silencieuse.
Où appliquer ça#
Un projet comme MissionMatch, où la frontière hexagonale est aujourd'hui une question de dossiers et de discipline d'équipe, est un candidat naturel pour ce genre de renforcement le jour où le nombre de contributeurs augmente. Tant que le projet reste piloté par une seule personne qui connaît chaque frontière par cœur, le dossier suffit. Dès qu'il ne l'est plus, faire vérifier la frontière par le compilateur devient plus fiable que de compter sur la mémoire de l'équipe.


