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
405 lines
11 KiB
Markdown
405 lines
11 KiB
Markdown
# 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)
|
|
- `Conformist`
|
|
- `Published Language`
|
|
|
|
Contraintes voulues:
|
|
|
|
- un seul repo
|
|
- `2 a 3 Bounded Contexts` maximum
|
|
- 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:
|
|
|
|
1. Un `Bounded Context` = un modele autonome + un langage propre.
|
|
2. Aucune entite/VO partagee entre BC par defaut.
|
|
3. Les relations entre BC sont explicites via une `Context Map`.
|
|
4. Le code inter-BC passe par des contrats (DTO, messages, API), jamais par les modeles internes.
|
|
5. Tout pattern (Conformist, ACL, OHS, Published Language) est rendu visible dans l'arborescence et dans les tests.
|
|
6. Les choix techniques Symfony/Doctrine restent remplaçables.
|
|
|
|
## 3. Domaine MiniShop (scope minimal)
|
|
|
|
### 3.1 Bounded Contexts retenus
|
|
|
|
- `Sales` (coeur metier): prise de commande
|
|
- `Invoicing`: emission de facture
|
|
- `LegacyFulfillment`: 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
|
|
|
|
1. Un client passe une commande dans `Sales`.
|
|
2. `Sales` notifie les autres BC qu'une commande est confirmee.
|
|
3. `Invoicing` produit une facture.
|
|
4. `LegacyFulfillment` cree 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):
|
|
|
|
- `Sales` appelle directement des services applicatifs externes
|
|
- schemas ad hoc, non versionnes
|
|
- couplage fort et fragile
|
|
|
|
Etat cible (apres tutoriels):
|
|
|
|
- `Sales` expose un `OHS` pour consultation/commande (option REST ou message)
|
|
- `Sales` publie un `Published Language` versionne (`sales.v1`)
|
|
- `Invoicing` suit `Conformist` sur ce langage publie
|
|
- `LegacyFulfillment` introduit un `ACL` entre langage publie et modele legacy
|
|
|
|
Representation synthese:
|
|
|
|
```text
|
|
Sales (Upstream)
|
|
├── Published Language (sales.v1.*)
|
|
├── OHS (query/command API)
|
|
├──> Invoicing (Conformist)
|
|
└──> LegacyFulfillment (ACL)
|
|
```
|
|
|
|
## 6. Architecture de code recommandee
|
|
|
|
```text
|
|
.
|
|
├── 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/Shared` ne 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.php`
|
|
- `contracts/sales/v1/Event/OrderConfirmed.php`
|
|
- `contracts/sales/v1/Api/OrderView.php`
|
|
|
|
Regles:
|
|
|
|
1. Contrats immutables.
|
|
2. Version explicite (`v1`, puis `v2`).
|
|
3. Compatibilite retro geree par ajout, pas mutation destructive.
|
|
4. 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)
|
|
|
|
- `Invoicing` consomme tel quel `sales.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 `AntiCorruption` cote `LegacyFulfillment`
|
|
- 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:
|
|
|
|
1. code compilable et testable
|
|
2. diagramme context map a jour (`docs/tutorials/`)
|
|
3. note "ce qui change" + "pourquoi Blue Book"
|
|
4. tag git pour reset rapide
|
|
|
|
## 11. Strategie de reset pour les tutos
|
|
|
|
Approche recommandee:
|
|
|
|
- une branche `main` stable (etat final ou etat de base choisi)
|
|
- des tags immuables `step/xx-*`
|
|
- une branche de travail recreee a chaque tuto depuis le tag
|
|
|
|
Exemple:
|
|
|
|
```bash
|
|
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)
|
|
|
|
```php
|
|
interface SalesEventPublisher
|
|
{
|
|
public function publishOrderConfirmed(OrderConfirmedMessage $message): void;
|
|
}
|
|
```
|
|
|
|
### 13.2 ACL vers legacy (Infrastructure)
|
|
|
|
```php
|
|
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)
|
|
|
|
```php
|
|
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'agregat
|
|
- `tests/Unit/Invoicing/Domain/*`: regles de facturation
|
|
- `tests/Unit/LegacyFulfillment/Infrastructure/AntiCorruption/*`: mapping ACL
|
|
- `tests/Contract/Sales/v1/*`: stabilite du langage publie
|
|
- `tests/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:
|
|
|
|
- `PlaceOrder`
|
|
- `ConfirmOrder`
|
|
- `CancelOrder`
|
|
|
|
Queries:
|
|
|
|
- `GetOrderById`
|
|
- `ListOrdersByCustomer`
|
|
|
|
Ports sortants:
|
|
|
|
- `OrderRepository`
|
|
- `SalesEventPublisher`
|
|
- `Clock`
|
|
- `TransactionManager`
|
|
|
|
Evenements d'integration publies:
|
|
|
|
- `sales.v1.OrderPlaced`
|
|
- `sales.v1.OrderConfirmed`
|
|
- `sales.v1.OrderCancelled`
|
|
|
|
### 16.2 Invoicing - cas d'usage
|
|
|
|
Commandes:
|
|
|
|
- `IssueInvoiceForExternalOrder`
|
|
- `MarkInvoiceAsSent`
|
|
|
|
Queries:
|
|
|
|
- `GetInvoiceByOrderRef`
|
|
|
|
Ports sortants:
|
|
|
|
- `InvoiceRepository`
|
|
- `InvoiceNumberGenerator`
|
|
|
|
Evenements d'integration consommes:
|
|
|
|
- `sales.v1.OrderConfirmed` (Conformist)
|
|
|
|
### 16.3 LegacyFulfillment - cas d'usage
|
|
|
|
Commandes:
|
|
|
|
- `RequestShipmentFromSalesOrder`
|
|
- `MarkShipmentDispatched`
|
|
|
|
Queries:
|
|
|
|
- `GetShipmentByExternalOrderRef`
|
|
|
|
Ports sortants:
|
|
|
|
- `ShipmentRequestRepository`
|
|
- `LegacyFulfillmentGateway`
|
|
|
|
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)
|
|
|
|
1. Implementer le domaine `Sales` (aggregate `Order`, invariants, repository in-memory).
|
|
2. Ajouter use case `PlaceOrder`, puis `ConfirmOrder`.
|
|
3. Brancher `Invoicing` en appel direct naif (episode 00).
|
|
4. Introduire `contracts/sales/v1` et migrer les echanges (episode 01).
|
|
5. Formaliser `Invoicing` en Conformist avec tests de contrat (episode 02).
|
|
6. Introduire `LegacyFulfillment` ACL et isoler tout mapping legacy (episode 03).
|
|
7. Exposer `Sales` via OHS versionne (episode 04).
|
|
8. 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.
|