Introduction au Domain-Driven Design pour des applications Spring Boot métier complexes
Le Domain-Driven Design (DDD) constitue une approche de conception logicielle visant à aligner étroitement le logiciel sur le métier. Lorsque les applications Spring Boot deviennent orientées processus, règles métier et invariants, les abstractions CRUD et l’architecture “modèle d’entités + services transactionnels” atteignent rapidement leurs limites. Le DDD propose alors de centrer la conception sur le modèle du domaine plutôt que sur la structure technique.
Pourquoi le DDD est utile dans un contexte Spring Boot
Dans les projets métiers complexes, la complexité ne réside pas uniquement dans la persistance, mais dans la signification des règles, leur évolution et leurs interactions. Le DDD aide à :
- Réduire le couplage entre couches techniques et concepts métier.
- Améliorer la lisibilité en reflétant le vocabulaire métier dans le code.
- Encapsuler les invariants via des Value Objects et des Aggregates.
- Favoriser l’évolution du modèle sans casser l’ensemble du système.
Les piliers du modèle DDD
Bounded Context et langage partagé
Un Bounded Context définit une frontière explicite où un modèle métier particulier est valable. La stratégie la plus efficace consiste à limiter le “langage partagé” à un périmètre clair. Le même terme métier peut avoir des règles différentes selon le contexte.
Entités, Value Objects et invariants
Le modèle DDD utilise des éléments orientés sémantique :
- Entités : identité persistante (ex. Commande, Facture).
- Value Objects : pas d’identité, uniquement une valeur (ex. Montant, Adresse).
- Invariants : règles permanentes maintenues par le modèle.
Aggregates, racines et cohérence transactionnelle
Les Aggregates sont conçus pour garantir la cohérence interne. Un aggregate possède une racine (aggregate root) responsable des transitions d’état. Les interactions externes passent par cette racine.
Architecture cible : Hexagonale/Ports & Adapters pour Spring Boot
Dans un projet Spring Boot, le DDD s’exprime souvent via une architecture hexagonale (ports & adapters). Le cœur métier (domain) ne dépend pas d’outils techniques. Les dépendances pointent vers l’extérieur via des interfaces.
Découpage recommandé
Une séparation claire améliore la maintenabilité :
- Domain : entités, value objects, aggregates, services de domaine.
- Application : orchestrateurs de cas d’usage, transaction scripts orchestrés, mapping d’entrée/sortie.
- Infrastructure : implémentations techniques (JPA, messaging, REST, clients externes).
findById(InvoiceId id);
Invoice save(Invoice invoice);
}
// Adapter : implémentation JPA côté infrastructure
@Repository
public class JpaInvoiceRepository implements InvoiceRepository {
private final SpringDataInvoiceJpaRepository repo;
@Override
public Optional findById(InvoiceId id) {
// Conversion entity <-> aggregate root
return repo.findById(id.value()).map(this::toDomain);
}
@Override
public Invoice save(Invoice invoice) {
// Conversion aggregate root <-> entity
return fromDomainToAndPersist(invoice);
}
}
]]>
Cas d’usage : Application Services et orchestration
Les Application Services portent la responsabilité des cas d’usage (ex. “Créer une facture”, “Enregistrer un paiement”, “Annuler une commande”). Ils orchestrent les ports du domaine et gèrent les transactions.
Le domaine contient la logique métier, tandis que l’application coordonne les appels, valide les paramètres d’entrée et déclenche les actions via les aggregates.
new NotFoundException("Facture introuvable"));
// La politique peut vivre dans le domaine (selon l'intention)
paymentPolicy.validate(cmd.payment());
invoice.registerPayment(cmd.payment());
invoices.save(invoice);
}
}
]]>
Stratégies de modélisation : éviter les pièges fréquents
Rich Domain Model vs anemic domain model
Un modèle anémique place la logique dans des services “script” et laisse le domaine comme simple ensemble de données. À l’inverse, un modèle riche place les invariants et comportements directement dans les entités, value objects et aggregates.
Réseau de dépendances implicites
La complexité augmente lorsque les dépendances techniques contaminent le domaine. La règle pratique : le domaine doit rester indépendant de JPA, Web, et frameworks. Les conversions et détails techniques se trouvent dans l’infrastructure.
Aggregates trop gros ou trop petits
Un aggregate trop grand devient difficile à maintenir et à faire évoluer. Un aggregate trop petit multiplie les interactions et peut briser la cohérence. Le bon niveau dépend des règles de cohérence métier.
Communication entre contextes
Quand plusieurs Bounded Context coexistent, les intégrations peuvent se faire via :
- Échanges synchrones (REST/gRPC) lorsque la latence et la cohérence temps réel sont nécessaires.
- Événements (pub/sub) lorsque la propagation asynchrone est acceptable.
- Traductions de modèle (anti-corruption layer) pour éviter que le vocabulaire d’un contexte contamine un autre.
Exemple de structure de code (suggestion)
Une organisation de packages cohérente facilite la navigation :
Conclusion
Le Domain-Driven Design fournit un cadre robuste pour construire des applications Spring Boot orientées métier. En clarifiant les frontières via les Bounded Context, en modélisant les règles avec des Aggregates, Value Objects et invariants, puis en isolant la logique métier grâce à une architecture de type ports & adapters, les systèmes gagnent en cohérence, en testabilité et en capacité d’évolution.
À propos de l'auteur
Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.
Contact : latygueyesamba@gmail.com | Dakar, Sénégal