3 Bounded Contexts (Sales, Invoicing, LegacyFulfillment) avec : - Domaines complets (agrégats, VOs, événements, invariants) - Couche application (commands, queries, ports) - Infrastructure in-memory (repos, gateway fake) - Controllers HTTP Symfony - Couplage naïf synchrone entre BC via NaiveSalesEventPublisher - 20 tests unitaires et d'intégration passants
11 KiB
MiniShop DDD Blue Book Boilerplate
Document de reference pour un boilerplate DDD minimaliste, pense pour PHP/Symfony, mais transposable.
1. But du boilerplate
Construire une base unique, reutilisable pour des tutoriels progressifs sur les patterns du Blue Book:
OHS(Open Host Service)ACL(Anti-Corruption Layer)ConformistPublished Language
Contraintes voulues:
- un seul repo
2 a 3 Bounded Contextsmaximum- interfaces explicites entre contextes
- integration volontairement naive au debut
- evolution pas a pas, concept par concept
2. Positionnement et principes
Ce boilerplate privilegie la purete tactique/strategique du DDD, pas la sophistication technique.
Principes directeurs:
- Un
Bounded Context= un modele autonome + un langage propre. - Aucune entite/VO partagee entre BC par defaut.
- Les relations entre BC sont explicites via une
Context Map. - Le code inter-BC passe par des contrats (DTO, messages, API), jamais par les modeles internes.
- Tout pattern (Conformist, ACL, OHS, Published Language) est rendu visible dans l'arborescence et dans les tests.
- Les choix techniques Symfony/Doctrine restent remplaçables.
3. Domaine MiniShop (scope minimal)
3.1 Bounded Contexts retenus
Sales(coeur metier): prise de commandeInvoicing: emission de factureLegacyFulfillment: expedition via systeme legacy volontairement "sale"
Shipping peut remplacer LegacyFulfillment dans une variante "moderne", mais la version pedagogique recommande LegacyFulfillment pour illustrer ACL.
3.2 Scenario fil rouge
- Un client passe une commande dans
Sales. Salesnotifie les autres BC qu'une commande est confirmee.Invoicingproduit une facture.LegacyFulfillmentcree une demande d'expedition dans un format legacy.
4. Ubiquitous Language par BC
4.1 Sales
- Entites/Aggregats:
Order - Value Objects:
OrderId,CustomerId,Money,OrderLine - Evenements de domaine:
OrderPlaced,OrderConfirmed - Invariants:
- une commande a au moins une ligne
- total > 0
- confirmation impossible si commande vide
4.2 Invoicing
- Entites/Aggregats:
Invoice - Value Objects:
InvoiceId,BillingParty,InvoiceLine,TaxRate - Evenements de domaine:
InvoiceIssued - Invariants:
- une facture refere une commande externe
- montant facture coherent avec ses lignes
4.3 LegacyFulfillment
- Entites/Aggregats:
ShipmentRequest - Value Objects:
LegacyOrderRef,ShippingAddress,ParcelSpec - Evenements de domaine:
ShipmentRequested - Contraintes:
- format legacy impose (champs abrégés, codifications date/status)
- mapping explicite depuis le langage publie
5. Context Map cible (pedagogique)
Etat initial (naif):
Salesappelle directement des services applicatifs externes- schemas ad hoc, non versionnes
- couplage fort et fragile
Etat cible (apres tutoriels):
Salesexpose unOHSpour consultation/commande (option REST ou message)Salespublie unPublished Languageversionne (sales.v1)InvoicingsuitConformistsur ce langage publieLegacyFulfillmentintroduit unACLentre langage publie et modele legacy
Representation synthese:
Sales (Upstream)
├── Published Language (sales.v1.*)
├── OHS (query/command API)
├──> Invoicing (Conformist)
└──> LegacyFulfillment (ACL)
6. Architecture de code recommandee
.
├── apps/
│ └── symfony/
│ ├── config/
│ └── public/
├── src/
│ ├── Sales/
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── Interfaces/
│ ├── Invoicing/
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── Interfaces/
│ ├── LegacyFulfillment/
│ │ ├── Domain/
│ │ ├── Application/
│ │ ├── Infrastructure/
│ │ └── Interfaces/
│ └── Shared/
│ └── Technical/ # utilitaires purement techniques (Clock, UUID)
├── contracts/
│ └── sales/
│ └── v1/
│ ├── Event/
│ └── Api/
├── tests/
│ ├── Unit/
│ ├── Integration/
│ └── Contract/
└── docs/
├── MINISHOP_DDD_BLUEBOOK_BOILERPLATE.md
└── tutorials/
Regle stricte:
src/Sharedne contient aucun concept metier transverse.- tout objet dans
contracts/est stable, versionnable et "publique" entre BC.
7. Convention de couches par BC
Domain:- modeles metier, invariants, services de domaine, evenements de domaine
- aucune dependance framework
Application:- use cases (commands/queries), orchestration, ports (interfaces)
Infrastructure:- persistance, bus, clients HTTP, adaptateurs externes
Interfaces:- HTTP CLI Messenger, serializers, controleurs
8. Contrats inter-BC (Published Language)
Le contrat publie minimal recommande:
contracts/sales/v1/Event/OrderPlaced.phpcontracts/sales/v1/Event/OrderConfirmed.phpcontracts/sales/v1/Api/OrderView.php
Regles:
- Contrats immutables.
- Version explicite (
v1, puisv2). - Compatibilite retro geree par ajout, pas mutation destructive.
- Tests de contrat obligatoires (
tests/Contract).
9. Evolution tutorielle (roadmap)
Episode 00 - Squelette + integration naive
- BC en place
- workflow
PlaceOrder -> Invoice -> LegacyShipment - appels synchrones directs inter-BC
- aucun Published Language officiel
Tag Git: step/00-naive
Episode 01 - Published Language
- extraction des DTO publics dans
contracts/sales/v1 - premiere version de messages d'integration
- tests de schema/serialisation
Tag Git: step/01-published-language
Episode 02 - Conformist (Invoicing)
Invoicingconsomme tel quelsales.v1- documentation explicite de la dependance upstream
- tests de non-regression de compatibilite
Tag Git: step/02-conformist
Episode 03 - ACL (LegacyFulfillment)
- creation d'une couche
AntiCorruptioncoteLegacyFulfillment - mapping
sales.v1 -> legacy command model - isolation des bizarreries legacy dans l'ACL
Tag Git: step/03-acl
Episode 04 - OHS (Sales)
- exposition d'un service hote explicite (
/api/sales/v1/...ou endpoint message) - documentation des points d'entree supportes
- modeles internes toujours non exposes
Tag Git: step/04-ohs
Episode 05 - Hardening
- outbox/event relay (optionnel selon niveau tuto)
- idempotence des consommateurs
- monitoring minimal (logs de correlation)
Tag Git: step/05-hardening
10. Definition of Done par episode
Chaque episode doit fournir:
- code compilable et testable
- diagramme context map a jour (
docs/tutorials/) - note "ce qui change" + "pourquoi Blue Book"
- tag git pour reset rapide
11. Strategie de reset pour les tutos
Approche recommandee:
- une branche
mainstable (etat final ou etat de base choisi) - des tags immuables
step/xx-* - une branche de travail recreee a chaque tuto depuis le tag
Exemple:
git fetch --tags
git switch -C play/episode-03 step/03-acl
12. Regles de purete Blue Book (checklist)
- Le modele de domaine de chaque BC est independant des autres BC.
- La relation upstream/downstream est documentee.
- Les dependances de code suivent la context map, pas l'inverse.
- Les traductions de langage sont localisees (ACL), jamais diffuses partout.
- Les contrats publies sont versionnes et testes.
- Les cas d'usage ne contournent pas les invariants d'agregat.
13. Gabarits minimaux utiles (pseudo-code)
13.1 Port de publication d'evenement (Application)
interface SalesEventPublisher
{
public function publishOrderConfirmed(OrderConfirmedMessage $message): void;
}
13.2 ACL vers legacy (Infrastructure)
final class LegacyShipmentAcl
{
public function fromSalesOrderConfirmed(OrderConfirmedMessage $message): LegacyCreateShipmentCommand
{
// Mapping explicite du langage publie vers le modele legacy.
}
}
13.3 Use case de Sales (Application)
final class ConfirmOrderHandler
{
public function __invoke(ConfirmOrderCommand $command): void
{
// Charge l'agregat, applique l'invariant, persiste, publie l'evenement d'integration.
}
}
14. Decoupage recommande des tests
tests/Unit/Sales/Domain/*: invariants d'agregattests/Unit/Invoicing/Domain/*: regles de facturationtests/Unit/LegacyFulfillment/Infrastructure/AntiCorruption/*: mapping ACLtests/Contract/Sales/v1/*: stabilite du langage publietests/Integration/*: wiring Symfony, bus, persistence
15. Ce que ce boilerplate n'essaie pas de faire
- couvrir tous les patterns strategiques DDD d'un coup
- fournir une architecture microservices complete
- optimiser prematurement la performance
La priorite est la clarte pedagogique, la reproductibilite et la fidelite Blue Book.
16. Specification minimale "codegen-ready"
16.1 Sales - cas d'usage
Commandes:
PlaceOrderConfirmOrderCancelOrder
Queries:
GetOrderByIdListOrdersByCustomer
Ports sortants:
OrderRepositorySalesEventPublisherClockTransactionManager
Evenements d'integration publies:
sales.v1.OrderPlacedsales.v1.OrderConfirmedsales.v1.OrderCancelled
16.2 Invoicing - cas d'usage
Commandes:
IssueInvoiceForExternalOrderMarkInvoiceAsSent
Queries:
GetInvoiceByOrderRef
Ports sortants:
InvoiceRepositoryInvoiceNumberGenerator
Evenements d'integration consommes:
sales.v1.OrderConfirmed(Conformist)
16.3 LegacyFulfillment - cas d'usage
Commandes:
RequestShipmentFromSalesOrderMarkShipmentDispatched
Queries:
GetShipmentByExternalOrderRef
Ports sortants:
ShipmentRequestRepositoryLegacyFulfillmentGateway
Evenements d'integration consommes:
sales.v1.OrderConfirmed(via ACL)
17. Matrice d'integration inter-BC
| Producteur | Contrat | Consommateur | Pattern | Notes |
|---|---|---|---|---|
| Sales | sales.v1.OrderPlaced |
(optionnel) Invoicing | Published Language | Peut etre ignore en phase initiale |
| Sales | sales.v1.OrderConfirmed |
Invoicing | Conformist | Invoicing s'aligne sur le schema upstream |
| Sales | sales.v1.OrderConfirmed |
LegacyFulfillment | ACL | Traduction vers format legacy |
| Sales | sales.v1 API |
clients externes | OHS | API stable et versionnee |
18. Plan d'implementation concret (ordre recommande)
- Implementer le domaine
Sales(aggregateOrder, invariants, repository in-memory). - Ajouter use case
PlaceOrder, puisConfirmOrder. - Brancher
Invoicingen appel direct naif (episode 00). - Introduire
contracts/sales/v1et migrer les echanges (episode 01). - Formaliser
Invoicingen Conformist avec tests de contrat (episode 02). - Introduire
LegacyFulfillmentACL et isoler tout mapping legacy (episode 03). - Exposer
Salesvia OHS versionne (episode 04). - Ajouter robustesse (idempotence, outbox optionnelle, correlation-id) (episode 05).
Ce document sert de "constitution" du repo MiniShop. Toute evolution de structure doit justifier son impact sur la context map et sur les patterns pedagogiques cibles.