Étude de cas
Application Bancaire Multi-Modules
Code source- 5 modules Maven, Java 21, Spring Boot 3.5.10.
- 45 classes réparties entre core, persistence, service, api et app.
- 10 anti-patterns typiques d'un découpage par couche technique.
Legacy
L'application legacy
Cette application bancaire multi-modules illustre un anti-pattern courant : le découpage horizontal par couche technique.
Chaque module correspond à une couche (modèle, persistance, services, API) plutôt qu'à un domaine métier. Le résultat est une application qui semble modulaire mais où le domaine reste invisible, noyé sous JPA et Spring.
Chaque module correspond à une couche (modèle, persistance, services, API) plutôt qu'à un domaine métier. Le résultat est une application qui semble modulaire mais où le domaine reste invisible, noyé sous JPA et Spring.
| # | Anti-pattern | |
|---|---|---|
| 1 | @Entity sur les classes domaine | Account, Customer, etc. héritent de BaseEntity (@MappedSuperclass) |
| 2 | Services sans distinction de rôle | Services applicatifs et domaine annotés @Service, aucune séparation |
| 3 | Aucun port d'abstraction | Les services dépendent directement des repositories Spring Data |
| 4 | Modèle anémique | Toute la logique métier vit dans les services, les entités sont de simples conteneurs |
| 5 | Primitives au lieu de value objects | BigDecimal pour les montants, String pour IBAN/BIC/email |
| 6 | Références directes entre agrégats | Account.customer = Customer (entité JPA) au lieu d'un identifiant typé |
Structure
Organisation des modules
L'application est découpée en 5 modules Maven suivant une logique purement technique :
Il n'existe aucun package
banking-core (modèle), banking-persistence (accès aux données), banking-service (logique), banking-api (exposition REST) et banking-app (démarrage).Il n'existe aucun package
domain, aucun port, aucun agrégat : la frontière entre métier et infrastructure est inexistante.banking/├── banking-core/ (14 classes)│ ├── model/ (10 classes : 6 entités, 3 enums, BaseEntity)│ ├── exception/ (3 classes)│ └── util/ (1 classe)├── banking-persistence/ (8 classes)│ ├── repository/ (6 interfaces Spring Data)│ └── config/ (2 classes)├── banking-service/ (8 classes)│ ├── service/ (7 classes)│ └── event/ (1 classe)├── banking-api/ (14 classes)│ ├── controller/ (5 classes)│ ├── dto/ (7 records + 1 response)│ └── exception/ (1 classe)└── banking-app/ (2 classes) └── BankingApplication + AppConfigDécoupage par couche technique au lieu d'un découpage par domaine métier.
- Les 10 classes de
banking-core/model/sont à la fois des entités JPA et des objets du domaine : le module "core" dépend de JPA - Les 7 services de
banking-service/concentrent l'orchestration, la logique métier et les appels aux repositories - Changer de base de données ou de framework implique de réécrire le domaine entier
Code legacy
Exemple : Account.java
La classe
C'est exactement ce que HexaGlue va analyser et signaler dans le rapport d'audit initial.
Account telle qu'elle existe dans le projet : annotations JPA, héritage de BaseEntity, primitives pour les montants et devises, référence directe à Customer au lieu d'un identifiant typé.C'est exactement ce que HexaGlue va analyser et signaler dans le rapport d'audit initial.
@Entity@Table(name = "accounts")public class Account extends BaseEntity {
@Column(nullable = false, unique = true) private String iban;
// Anti-pattern : référence directe à l'entité au lieu d'un identifiant typé @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "customer_id", nullable = false) private Customer customer;
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL) private List<Transaction> transactions = new ArrayList<>();
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL) private List<Card> cards = new ArrayList<>();
@Enumerated(EnumType.STRING) @Column(nullable = false) private AccountType type;
@Column(precision = 15, scale = 2) private BigDecimal balance;
private String currency;
// getters, setters...}- Domaine invisible sous les annotations JPA (
@Entity,@Table,@ManyToOne) - Couplage fort :
Accountréférence l'entitéCustomerau lieu d'un identifiant typéCustomerId - Absence de typage métier :
BigDecimalpour le solde,Stringpour l'IBAN et la devise - Héritage technique imposé par
BaseEntity(@MappedSuperclass) avecLong idauto-généré - Aucun comportement métier : uniquement des getters/setters, la logique vit dans les services
Premier diagnostic
Brancher HexaGlue sur le projet
Pour obtenir un diagnostic architectural, on ajoute le plugin HexaGlue au POM parent et on déclare le rôle de chaque module dans
En mode multi-module, HexaGlue détecte automatiquement la structure reactor et produit un rapport d'audit cross-module en une seule commande
hexaglue.yaml.En mode multi-module, HexaGlue détecte automatiquement la structure reactor et produit un rapport d'audit cross-module en une seule commande
mvn verify.<plugin> <groupId>io.hexaglue</groupId> <artifactId>hexaglue-maven-plugin</artifactId> <version>6.1.1</version> <extensions>true</extensions> <configuration> <basePackage>com.acme.banking</basePackage> <failOnError>false</failOnError> </configuration> <dependencies> <dependency> <groupId>io.hexaglue.plugins</groupId> <artifactId>hexaglue-plugin-audit</artifactId> <version>3.1.1</version> </dependency> </dependencies></plugin># hexaglue.yaml (racine du projet multi-module)modules: banking-core: role: DOMAIN banking-persistence: role: INFRASTRUCTURE banking-service: role: APPLICATION banking-api: role: API banking-app: role: ASSEMBLYextensions=true: HexaGlue détecte le POM parent multi-module et injecte automatiquement les goalsreactor-generateetreactor-auditbasePackage: toutes les classes souscom.acme.bankingseront analysées et classifiées par HexaGluefailOnError=false: le build ne sera pas bloqué par les violations, utile pour un premier diagnostic non intrusifhexaglue.yaml: chaque module reçoit un rôle architectural (DOMAIN, INFRASTRUCTURE, APPLICATION, API, ASSEMBLY) qui permet de valider les dépendances entre couches
Rapport d'audit
Résultat de la première exécution de l'audit HexaGlue en mode reactor sur les 5 modules du projet bancaire.
Verdict
Verdict de l'audit initial
Le score global, la note et les violations sont affichés en tête du rapport. Ce premier verdict donne une photographie chiffrée de l'état architectural avant toute modification.
14
/100
Grade F
FAILED
36
violations
13
critical
23
major
Le grade F confirme que l'architecture ne respecte aucun principe DDD ni hexagonal. Les sections suivantes détaillent la décomposition du score et les violations par catégorie.
Score
Décomposition du score
Le score de 14/100 est calculé à partir de 5 dimensions pondérées. Ce tableau montre la contribution de chaque dimension et son écart par rapport au seuil attendu.
| Dimension | Score | Status | |
|---|---|---|---|
| DDD Compliance | 9% | ||
| Hexagonal Architecture | 0% | ||
| Dependencies | 0% | ||
| Coupling | 40% | ||
| Cohesion | 40% | ||
| TOTAL | 14.25 | ||
- DDD Compliance et Hexagonal Architecture à 0% : ces deux dimensions représentent 50% du score total. Aucun agrégat, aucun port et aucun identifiant typé n'existe dans le code
- Dependencies à 0% : les dépendances vont du domaine vers l'infrastructure, l'inverse de ce qu'attend l'architecture hexagonale
- Coupling et Cohesion contribuent légèrement : la séparation en modules Maven apporte un minimum de découplage structurel
Violations
Violations détectées
Chaque violation correspond à une règle architecturale enfreinte par le code. Elles sont classées par sévérité : Critical pour les problèmes structurels fondamentaux, Major pour les écarts significatifs qui bloquent la progression vers une architecture hexagonale.
| Contrainte | Nombre Nb | Sévérité Sév. |
|---|---|---|
| ddd:domain-purity | 7 | CRITICAL CRIT |
| ddd:entity-identity | 6 | CRITICAL CRIT |
| hexagonal:port-coverage | 6 | MAJOR MAJ |
| hexagonal:layer-isolation | 11 | MAJOR MAJ |
| hexagonal:port-direction | 6 | MAJOR MAJ |
Les 13 violations Critical ont toutes la même cause racine : le domaine dépend directement de l'infrastructure.
- ddd:entity-identity : les 6 entités utilisent un
Long idauto-généré par JPA, pas un identifiant métier typé - ddd:domain-purity : le domaine importe
jakarta.persistence, il dépend structurellement de la couche de persistance
Les 23 violations Major signalent l'absence de structure hexagonale. La prochaine étape commence par configurer les exclusions pour éliminer le bruit d'analyse, puis restructure le code en couches hexagonales.