É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.
#Anti-pattern
1@Entity sur les classes domaine
2Services sans distinction de rôle
3Aucun port d'abstraction
4Modèle anémique
5Primitives au lieu de value objects
6Références directes entre agrégats
Structure

Organisation des modules

L'application est découpée en 5 modules Maven suivant une logique purement technique : 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 + AppConfig

Dé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 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.
Account.java
@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 : Account référence l'entité Customer au lieu d'un identifiant typé CustomerId
  • Absence de typage métier : BigDecimal pour le solde, String pour l'IBAN et la devise
  • Héritage technique imposé par BaseEntity (@MappedSuperclass) avec Long id auto-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 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.
pom.xml (parent)
<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
# 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: ASSEMBLY
  • extensions=true : HexaGlue détecte le POM parent multi-module et injecte automatiquement les goals reactor-generate et reactor-audit
  • basePackage : toutes les classes sous com.acme.banking seront analysées et classifiées par HexaGlue
  • failOnError=false : le build ne sera pas bloqué par les violations, utile pour un premier diagnostic non intrusif
  • hexaglue.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.
DimensionScoreStatus
DDD Compliance9%
Hexagonal Architecture0%
Dependencies0%
Coupling40%
Cohesion40%
TOTAL14.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 Nb Sév.
ddd:domain-purity7 CRIT
ddd:entity-identity6 CRIT
hexagonal:port-coverage6 MAJ
hexagonal:layer-isolation11 MAJ
hexagonal:port-direction6 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 id auto-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.