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 réorganisée selon les principes de l'architecture hexagonale :
les packages
les packages
domain/, ports/, application/ et infrastructure/ séparent le domaine de l'infrastructure, et les services existants sont refactorisés en services applicatifs qui orchestrent les cas d'usage via des ports.Domain
- Déplacer 13 classes domaine (vers domain/order/, domain/customer/, domain/product/, domain/inventory/, domain/payment/, domain/shipping/)
Ports
- Définir 7 driving ports (OrderUseCases, CustomerUseCases, ProductUseCases, InventoryUseCases, PaymentUseCases, ShippingUseCases, CartUseCases dans ports/in/)
- Définir 8 driven ports (OrderRepository, CustomerRepository, ProductRepository, InventoryRepository, PaymentRepository, ShipmentRepository, NotificationSender, PaymentGateway dans ports/out/)
Application
- Créer 7 services applicatifs (OrderApplicationService, CustomerApplicationService, etc. dans application/)
Adapters
- Implémenter 6 adaptateurs JPA (JpaOrderRepository extends JpaRepository, OrderRepository dans infrastructure/persistence/)
- Implémenter 2 adaptateurs externes (PaymentGatewayAdapter, NotificationAdapter dans infrastructure/external/)
- Driving ports (ports entrants) : interfaces qui exposent les cas d'usage au monde extérieur, par exemple
OrderUseCases - Driven ports (ports sortants) : interfaces qui abstraient les dépendances externes, par exemple
OrderRepositoryouNotificationSender
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 OrderUseCases { OrderId createOrder(CustomerId customerId, String currency); void addLine(OrderId orderId, ProductId productId, Quantity quantity, Money unitPrice); void placeOrder(OrderId orderId, Address shippingAddress); void cancelOrder(OrderId orderId, String reason); Order getOrder(OrderId orderId); List<Order> getOrdersByCustomer(CustomerId customerId);}- L'interface ne contient que du métier : pas d'annotation Spring, pas d'import JPA
- Les types utilisés (
OrderId,CustomerId,Money) 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 (OrderId, CustomerId, ProductId, PaymentId, ShipmentId, InventoryId (records Java avec validation))
- Créer 4 value objects (Money, Address, Email, Quantity avec validation et comportement métier)
- Créer 1 domain event (OrderPlacedEvent capturant le fait métier avec OrderId, CustomerId et Money)
- Purifier 9 classes domaine (suppression @Entity / @ManyToOne / extends BaseEntity, ajout logique métier (place(), cancel()...))
- Supprimer BaseEntity (chaque agrégat définit son propre identifiant typé, la hiérarchie technique disparaît)
Adapters
- Supprimer 6 repositories JPA (JpaOrderRepository, JpaCustomerRepository, etc. supprimés car le domaine purifié n'est plus compatible)
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 issus deBaseEntity, 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 multiplier des montants - Agrégats purifiés : protègent leurs invariants via des méthodes métier (
place(),markPaid(),cancel()) - Repositories JPA supprimés : le domaine purifié n'est plus compatible avec les anciens adaptateurs, ils seront régénérés par HexaGlue
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
OrderId comme IDENTIFIER et Money comme VALUE_OBJECT.public record OrderId(Long value) { public OrderId { if (value == null) { throw new IllegalArgumentException("OrderId value must not be null"); } }}public record Money(BigDecimal amount, String currency) { public Money { if (amount == null) throw new IllegalArgumentException("Amount must not be null"); if (currency == null) throw new IllegalArgumentException("Currency must not be null"); amount = amount.setScale(2, RoundingMode.HALF_UP); }
public static Money zero(String currency) { return new Money(BigDecimal.ZERO, currency); }
public Money add(Money other) { requireSameCurrency(other); return new Money(this.amount.add(other.amount), this.currency); }
public Money multiply(int quantity) { return new Money(this.amount.multiply(BigDecimal.valueOf(quantity)), this.currency); }
private void requireSameCurrency(Money other) { if (!this.currency.equals(other.currency)) { throw new IllegalArgumentException("Currency mismatch"); } }}- Le record remplace le
Longgénérique hérité deBaseEntity: on ne peut plus confondre unOrderIdavec unCustomerIdà la compilation - La validation au constructeur garantit qu'aucun identifiant nul ni montant invalide ne circule dans le domaine
Money.add()etMoney.multiply()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