DDD stratégique — carnet de terrain
Ce qui se décide avant de coder, avec des gens qui ne sont pas développeurs. Écrit pour être fait, pas seulement lu.
Pourquoi ce document existe séparément#
Un cours adossé à un dépôt ne peut pas enseigner le stratégique, pour une raison structurelle : le stratégique ne produit pas de code.
Il produit des décisions de frontières. Or une frontière ne se lit pas dans un fichier — elle se lit dans ce que plusieurs personnes entendent quand elles disent le même mot. Un dépôt montre le résultat d'un découpage ; il ne peut montrer ni le raisonnement qui l'a produit, ni les trois découpages écartés.
D'où le déséquilibre assumé : le cours consacre treize chapitres au tactique et une section au stratégique. Ce carnet corrige le tir dans le seul format qui a du sens — une procédure à exécuter, pas une explication à comprendre.
Ce que le stratégique décide réellement#
Trois décisions, et rien d'autre :
| Décision | Question à laquelle elle répond | Livrable |
|---|---|---|
| Langage ubiquitaire | Quel mot pour quelle chose, et qui l'emploie ? | Un glossaire par contexte |
| Bounded contexts | Où passent les frontières de sens ? | Une liste de contextes nommés |
| Context map | Comment les contextes se parlent, et qui subit qui ? | Un schéma de relations |
Ce ne sont pas des artefacts de documentation. Ce sont des contrats : le glossaire dit ce que le code a le droit de nommer, les contextes disent ce qui a le droit de dépendre de quoi, la context map dit qui casse qui en cas de changement.
La définition qui sert vraiment#
Un bounded context est une frontière à l'intérieur de laquelle un modèle et son langage sont cohérents, et dont les règles de signification sont maîtrisées.
L'indice le plus facile à observer tient en une phrase : si deux personnes emploient le même mot pour deux choses différentes, examine une séparation. Pas « elles devraient s'accorder » — elles ont raison toutes les deux, dans leur contexte. Attention toutefois : c'est un signal fort, pas une preuve. La frontière se décide sur le modèle, pas sur un lexique.
Les symptômes de frontières fausses#
Avant de lancer un atelier, vérifie qu'il est nécessaire. Voici les symptômes observables — certains directement dans le dépôt.
| Symptôme | Ce qu'il révèle | Comment le repérer |
|---|---|---|
Une classe centrale que tout le monde importe (User, Order, Product) | Plusieurs contextes fusionnés de force | grep -rc "import User" — si c'est partout, c'est le signal |
| Des champs optionnels par groupes (« ces 6 champs ne servent qu'à la facturation ») | Deux concepts dans une classe | Lire les | None et voir s'ils se regroupent |
| Un même mot défini différemment selon l'interlocuteur | Frontière de sens non nommée | Écouter deux réunions métier |
| Des commits qui touchent systématiquement 5 modules | Frontière au mauvais endroit | git log --name-only sur 3 mois |
| Un « service » ou un « manager » qui grossit sans fin | Le contexte manquant est là, sans nom | Chercher les classes > 300 lignes |
| Des tables jointes en permanence dans les requêtes | Cohésion réelle non reflétée | Lire les JOIN récurrents |
Aucun de ces symptômes ? Il n'y a probablement pas de problème stratégique — et ce document peut être rangé. C'est le cas de ce dépôt aujourd'hui, pour une raison développée plus bas.
La procédure#
Compter une journée pour un système de taille moyenne, ou deux demi-journées espacées d'une semaine — mieux : le cerveau travaille entre les deux.
Étape 0 — Réunir les bonnes personnes#
C'est l'étape qu'on rate le plus souvent, et elle décide de tout le reste.
Qui doit être là :
- 1 à 3 experts métier — les gens qui font le travail, pas ceux qui le managent. Le commercial qui saisit les commandes vaut mieux que le directeur commercial.
- 2 à 5 développeurs — dont ceux qui maintiendront le code.
- 1 facilitateur — qui ne prend pas parti sur le modèle, et dont le seul rôle est de faire parler les experts et d'empêcher les développeurs de parler technique.
Qui ne doit pas être là : personne qui a un intérêt à défendre une architecture existante — y compris l'auteur du code actuel, d'où le facilitateur externe.
Le matériel : un mur, des post-its de 4 couleurs, des marqueurs. Pas d'écran, pas de Miro tant que le mur physique est possible. La contrainte physique force la synthèse ; l'infini du canvas numérique la dissout.
La règle d'or : les développeurs écoutent et écrivent, ils ne proposent pas de modèle. Dès qu'on entend « on pourrait faire une table… », le facilitateur coupe.
Étape 1 — EventStorming big picture (2 à 3 h)#
- Les événements (post-its orange). Chaque participant écrit tout ce qui arrive dans le système, au passé : « Commande passée », « Paiement accepté », « Colis expédié », « Tâche terminée ». Pas de discussion pendant 20 minutes : on écrit en silence et on colle.
- La ligne de temps. On ordonne les post-its de gauche à droite. Les doublons apparaissent, les trous aussi. C'est bruyant, et c'est normal.
- Les points chauds (post-its rouges). À chaque désaccord ou question sans réponse : un post-it rouge, et on avance. Ne jamais résoudre un désaccord sur le moment — le noter est plus précieux que le trancher.
- Les acteurs et les systèmes externes. Qui déclenche quoi, qui reçoit quoi.
Livrable : un mur d'événements ordonnés, photographié. Et la liste des points rouges — qui vaut souvent plus que le reste.
Étape 2 — Repérer les ruptures#
C'est ici que les frontières apparaissent. Trois choses à chercher sur le mur :
- Les ruptures de vocabulaire. Le moment où « client » cesse de vouloir dire prospect à convaincre pour vouloir dire adresse de livraison.
- Les pivots temporels. Les endroits où le processus attend : validation humaine, paiement externe, livraison. Une attente est presque toujours une frontière — parce que la cohérence immédiate y est déjà impossible.
- Les changements d'acteur. Quand la personne qui agit change, le vocabulaire change presque toujours avec.
L'exercice décisif : prendre chaque nom important et demander à voix haute « est-ce que ça veut dire la même chose ici et là ? ». Noter chaque « ah non, pas tout à fait ». Chaque « pas tout à fait » est une frontière à examiner.
Étape 3 — Nommer les contextes candidats#
Entourer des zones du mur, leur donner un nom issu du métier
(Facturation, Expédition, Catalogue) —
jamais un nom technique (Core, Common,
Referentiel).
Puis, pour chacun, écrire le glossaire : 5 à 15 termes, leur définition dans ce contexte. C'est le livrable qui survit le mieux au temps ; c'est souvent le seul artefact encore lu deux ans après.
Un contexte sans glossaire rédigé n'est pas un contexte, c'est un dossier.
Étape 4 — Dessiner la context map#
| Relation | Ce que ça veut dire | Quand la choisir | Coût |
|---|---|---|---|
| Shared kernel | Un petit noyau commun, modifié d'un commun accord | Équipes très proches, noyau vraiment stable | Élevé à terme : tout changement se négocie |
| Customer / supplier | L'amont s'engage à servir l'aval ; l'aval peut négocier | Deux équipes, dépendance assumée | Moyen : demande une vraie relation |
| Conformist | L'aval adopte tel quel le modèle de l'amont | L'amont ne bougera jamais (SI historique, API tierce) | Faible à court terme, empoisonnant à long terme |
| Anti-corruption layer | L'aval traduit le modèle amont dans le sien | Le modèle amont est inadapté ou instable | Le plus coûteux à écrire, le moins coûteux à vivre |
Ajouter une flèche par relation, et écrire le sens de la dépendance — c'est-à-dire qui casse qui quand ça change. C'est l'information la plus utile de tout le schéma.
L'anti-corruption layer est celle à connaître par cœur. C'est exactement le principe que le cours applique partout à petite échelle : un mapper protège le domaine de l'ORM, un ACL protège un contexte du modèle d'un autre système. Chaque fois qu'on intègre quelque chose qu'on ne contrôle pas — un CRM, une API de paiement, le SI d'un client — la question n'est pas si on écrit un ACL, mais quand on regrettera de ne pas l'avoir fait.
Étape 5 — Décider quoi implémenter, et dans quel ordre#
Tous les contextes identifiés ne méritent pas d'exister dans le code aujourd'hui. Les classer :
- Le cœur (core domain) — ce qui fait la valeur, ce que personne d'autre ne fait à ta place. C'est là que va l'effort tactique du cours : agrégats, invariants, tests. Il y en a un, rarement deux.
- Le support — nécessaire mais banal. Un CRUD honnête suffit (ch. 13).
- Le générique — auth, facturation, notifications. Acheter, ne pas écrire.
L'erreur classique est d'appliquer le traitement complet du cours aux trois catégories. Le vrai geste stratégique, c'est de décider où ne pas investir.
Les tests de validation d'une frontière#
Avant d'acter un découpage, le passer à ces cinq tests. Chacun peut le disqualifier à lui seul.
- Le test du mot — un terme a-t-il exactement un sens dans ce contexte ? S'il en a deux, la frontière est probablement mal placée. Signal fort, pas preuve : c'est la cohérence du modèle qui tranche, pas le lexique seul.
- Le test de la transaction — ce qui doit être cohérent immédiatement est-il à l'intérieur ? Si une règle exige une cohérence instantanée entre deux contextes, ils n'en font qu'un (ch. 04).
- Le test de l'équipe — une équipe peut-elle posséder ce contexte de bout en bout ? Corollaire de la loi de Conway : le découpage finira par ressembler à l'organigramme, qu'on le veuille ou non. Autant le choisir.
- Le test du déploiement — pourrait-il être déployé seul, en théorie ? Il n'y a pas obligation de le faire. Mais si la réponse est « impossible », la frontière est fictive.
- Le test du glossaire — peut-on écrire 10 termes définis sans jamais renvoyer à un autre contexte ? Si chaque définition dit « voir le contexte X », il n'y a qu'un contexte.
Ce que ça change dans le code#
Ce qui change :
- Un package racine par contexte, avec sa propre tranche verticale complète.
- Aucun import direct d'un contexte vers un autre, sauf
identités. Ce dépôt respecte déjà ça :
taskimporteUserIdet rien d'autre. - Un contrat explicite à chaque frontière : identité partagée, événement d'intégration, ou ACL.
- Une règle import-linter par frontière, pour que ça reste vrai (ch. 01).
Ce qui ne change pas nécessairement :
- Le nombre de bases de données. Un contexte peut vivre dans un schéma logique de la même base. Séparer physiquement est une décision de déploiement, pas de modélisation.
- Le nombre de services déployés. Un monolithe modulaire à 4 contextes est souvent supérieur à 4 microservices — et infiniment plus facile à corriger quand une frontière s'avère fausse.
- L'existence d'un shared kernel minimal. Quelques classes sans logique (bases d'exceptions, types techniques) sont acceptables. Dès qu'une règle y entre, il faut le découper.
Diagnostic : ce dépôt a-t-il deux bounded contexts ?#
task et user sont souvent présentés comme deux bounded
contexts. Passons-les aux cinq tests ci-dessus :
| Test | Résultat |
|---|---|
| Le mot | non User a un seul sens dans tout le système : le propriétaire d'une tâche. Aucune rupture de vocabulaire. |
| La transaction | non CreateTask vérifie l'existence du user dans la même transaction, et une FK ON DELETE CASCADE lie les tables. |
| L'équipe | non Une seule équipe, un seul dépôt. |
| Le déploiement | non Un service, une base, une migration 001_initial_schema.py qui crée les deux tables. |
| Le glossaire | non Impossible d'écrire 10 termes de task sans parler de user, et réciproquement. |
Verdict : ce sont deux agrégats dans un seul bounded context, organisés en modules. Pas deux contextes.
Pourquoi ce n'est pas grave — et pourquoi il faut le savoir#
Ce n'est pas grave, pour trois raisons :
- Le découpage est bon quand même. Deux agrégats bien séparés, référencés par identité, sans import croisé : c'est exactement ce qu'il faut faire. Le travail est juste ; c'est l'étiquette qui est trop ambitieuse.
- Le métier ne justifie pas deux contextes. Le dépôt le dit lui-même : « le métier est volontairement simple ». Un domaine à deux entités n'a structurellement pas assez de sens pour avoir deux vocabulaires.
- Ça prépare bien la suite. Le fait que
taskne connaisse qu'unUserIdrend l'extraction future presque gratuite. C'est le bénéfice réel, et il est acquis.
Mais il faut le savoir, pour une raison précise : ce dépôt se présente comme une référence à reproduire. Quelqu'un qui le copie apprend « bounded context = sous-dossier par entité ». C'est le contresens exact que le chapitre 03 essaie d'éviter.
À quoi ressemblerait un vrai second contexte ici#
Le candidat évident est absent du dépôt : l'authentification. Un
grep sur auth|jwt|token|password dans src/ ne
renvoie rien. C'est le cas d'école parfait, parce que le mot User y
prendrait enfin un second sens :
Contexte identity | Contexte task | |
|---|---|---|
User désigne | un compte : identifiants, mot de passe haché, sessions, rôles | un propriétaire : un nom, un e-mail de contact |
| Cycle de vie | création, activation, verrouillage, suppression RGPD | existe tant qu'il a des tâches |
| Ce qui traverse | UserId, et rien d'autre | — |
Voilà une vraie rupture de vocabulaire, donc une vraie frontière. Elle justifierait un ACL minuscule et démontrerait le stratégique par le code.
Ce qu'il ne faut pas en conclure#
- Qu'il faudrait découper
tasketuseren deux services. Aucun des cinq tests ne le justifie, et ça coûterait la cohérence référentielle (la FKON DELETE CASCADEdeviendrait un processus de compensation). - Qu'il faudrait ajouter des contextes pour la démonstration. Un
billingsans besoin réel produirait exactement le mauvais exemple : des frontières décoratives. - Que les dossiers sont mal nommés.
domain/task/etdomain/user/sont très bien. C'est le vocabulaire employé autour qui sur-vend, pas la structure.
Pour aller plus loin#
- Domain-Driven Design (Eric Evans) — parties IV et V, les deux tiers du livre que presque personne ne lit. C'est là qu'est le stratégique.
- Domain-Driven Design Distilled (Vaughn Vernon) — 150 pages, va droit au stratégique. Le meilleur rapport temps/valeur du domaine.
- Introducing EventStorming (Alberto Brandolini) — la méthode de l'étape 1, par son auteur.
- Team Topologies (Skelton & Pais) — le lien entre frontières logicielles et frontières d'équipes ; le complément moderne indispensable au chapitre « contextes » d'Evans.