Migration hexagonale
Migration hexagonale
Deux étapes fondamentales : la restructuration hexagonale pose le squelette des ports et services applicatifs, puis la purification du domaine élimine toutes les violations DDD.
Restructuration
Restructuration hexagonale
L'application est découpée selon les principes de l'architecture hexagonale :
des ports (interfaces) séparent le domaine de l'infrastructure, et les services existants sont refactorisés en services applicatifs qui orchestrent les cas d'usage via ces ports.
des ports (interfaces) séparent le domaine de l'infrastructure, et les services existants sont refactorisés en services applicatifs qui orchestrent les cas d'usage via ces ports.
Ports
- Définir 5 driving ports (AccountUseCases, CardUseCases, CustomerUseCases, TransactionUseCases, TransferUseCases dans banking-core/port/in/)
- Définir 8 driven ports (AccountRepository, BeneficiaryRepository, CardRepository, CustomerRepository, TransactionRepository, TransferRepository, FraudDetection, NotificationSender dans banking-core/port/out/)
Application
- Créer 5 services applicatifs (XxxService refactorisé en XxxApplicationService dans banking-service/application/)
Adapters
- Déplacer 6 adaptateurs JPA (XxxRepository renommé en JpaXxxRepository dans banking-persistence/)
- Driving ports (ports entrants) : interfaces qui exposent les cas d'usage au monde extérieur, par exemple
AccountUseCases - Driven ports (ports sortants) : interfaces qui abstraient les dépendances externes, par exemple
AccountRepositoryouFraudDetection
Cette inversion de dépendance permet au domaine de ne dépendre que d'abstractions, pas d'implémentations concrètes.
Code
Exemple de driving port
Un driving port définit un cas d'usage métier sans référence à une technologie.
HexaGlue détecte automatiquement ce pattern et classe l'interface comme
HexaGlue détecte automatiquement ce pattern et classe l'interface comme
DRIVING_PORT.public interface AccountUseCases { AccountId openAccount(CustomerId customerId, AccountType type, String currency); void deposit(AccountId accountId, BigDecimal amount); void withdraw(AccountId accountId, BigDecimal amount); Account getAccount(AccountId accountId); List<Account> getAccountsByCustomer(CustomerId customerId); void closeAccount(AccountId accountId);}- L'interface ne contient que du métier : pas d'annotation Spring, pas d'import JPA
- Les types utilisés (
AccountId,CustomerId) sont des concepts domaine, pas des primitifs - HexaGlue détecte ce pattern et le fait apparaître dans l'inventaire architectural comme
DrivingPort
Purification
Purification du domaine
Le domaine est purifié : toutes les annotations JPA sont supprimées et remplacées par des patterns tactiques DDD.
Les identifiants typés et les value objects permettent à HexaGlue d'inférer la classification complète sans aucune annotation explicite.
Les identifiants typés et les value objects permettent à HexaGlue d'inférer la classification complète sans aucune annotation explicite.
Domain
- Créer 6 identifiants typés (AccountId, BeneficiaryId, CardId, CustomerId, TransactionId, TransferId (records Java))
- Créer 4 value objects (Money, Iban, Email, Address avec validation et comportement métier)
- Purifier 6 entités domaine (suppression @Entity / @Id, ajout logique métier (Account.deposit(), Account.withdraw()))
- Supprimer 2 classes utilitaires (IbanUtils et MoneyUtils remplacés par les value objects Iban et Money)
Adapters
- Créer 6 entités JPA séparées (JpaAccount, JpaBeneficiary, JpaCard, JpaCustomer, JpaTransaction, JpaTransfer dans banking-persistence/entity/)
C'est la transformation fondamentale du DDD : séparer le modèle métier de l'infrastructure.
- Identifiants typés (records) : remplacent les
Longgénériques, chaque agrégat a son propre type d'identifiant - Value objects : encapsulent les règles de validation et le comportement, par exemple
Moneysait additionner et vérifier la devise - Entités domaine purifiées : protègent leurs invariants via des méthodes métier (
deposit(),withdraw()) - Classes utilitaires supprimées :
IbanUtilsetMoneyUtilsdisparaissent au profit des value objects
Code
Identifiants typés et value objects
Deux patterns clés illustrent la purification du domaine.
HexaGlue détecte ces structures automatiquement et classe
HexaGlue détecte ces structures automatiquement et classe
AccountId comme IDENTIFIER et Money comme VALUE_OBJECT.public record AccountId(Long value) { public AccountId { Objects.requireNonNull(value, "AccountId value must not be null"); }}public record Money(BigDecimal amount, String currency) { public Money { Objects.requireNonNull(amount, "amount must not be null"); Objects.requireNonNull(currency, "currency must not be null"); if (amount.scale() > 2) { throw new IllegalArgumentException("amount scale must be <= 2"); } }
public Money add(Money other) { requireSameCurrency(other); return new Money(amount.add(other.amount), currency); }
public Money subtract(Money other) { requireSameCurrency(other); return new Money(amount.subtract(other.amount), currency); }
private void requireSameCurrency(Money other) { if (!currency.equals(other.currency)) { throw new IllegalArgumentException("Currency mismatch"); } }}- Le record remplace le
Longgénérique : on ne peut plus confondre unAccountIdavec unCustomerIdà la compilation - La validation au constructeur garantit qu'aucun identifiant nul ni montant invalide ne circule dans le domaine
Money.add()etMoney.subtract()retournent une nouvelle instance : l'immutabilité est garantie par le record- Ces deux patterns sont détectés automatiquement par HexaGlue sans aucune annotation supplémentaire