FastAPI Hexagonal Code source

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.

Ce document n'est pas un chapitre du cours. Le cours enseigne le DDD tactique : ce qui se code. Ce carnet traite du DDD stratégique : les frontières. Il se termine par un diagnostic sans complaisance de ce dépôt.

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.

Le vrai enjeu, en une phrase Se tromper de pattern coûte une refonte de classe. Se tromper de frontière coûte une refonte de système. Un modèle tactique impeccable posé sur de mauvaises frontières produit un système propre localement et incohérent globalement — le pire cas, parce qu'il a l'air bon.

Ce que le stratégique décide réellement#

Trois décisions, et rien d'autre :

DécisionQuestion à laquelle elle répondLivrable
Langage ubiquitaireQuel mot pour quelle chose, et qui l'emploie ?Un glossaire par contexte
Bounded contextsOù passent les frontières de sens ?Une liste de contextes nommés
Context mapComment 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.

Le réflexe à désapprendre Chercher le modèle unique qui satisfait tout le monde. Il n'existe pas, et le poursuivre produit toujours la même chose : une classe qui grossit, pleine de champs optionnels qui n'ont de sens que pour la moitié des appelants, que plus personne n'ose modifier. Le dédoublement d'un concept entre deux contextes n'est pas de la duplication à éliminer.

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ômeCe qu'il révèleComment le repérer
Une classe centrale que tout le monde importe (User, Order, Product)Plusieurs contextes fusionnés de forcegrep -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 classeLire les | None et voir s'ils se regroupent
Un même mot défini différemment selon l'interlocuteurFrontière de sens non nomméeÉcouter deux réunions métier
Des commits qui touchent systématiquement 5 modulesFrontière au mauvais endroitgit log --name-only sur 3 mois
Un « service » ou un « manager » qui grossit sans finLe contexte manquant est là, sans nomChercher les classes > 300 lignes
Des tables jointes en permanence dans les requêtesCohésion réelle non reflétéeLire 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à :

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)#

  1. 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.
  2. 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.
  3. 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.
  4. 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 :

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#

RelationCe que ça veut direQuand la choisirCoût
Shared kernelUn petit noyau commun, modifié d'un commun accordÉquipes très proches, noyau vraiment stableÉlevé à terme : tout changement se négocie
Customer / supplierL'amont s'engage à servir l'aval ; l'aval peut négocierDeux équipes, dépendance assuméeMoyen : demande une vraie relation
ConformistL'aval adopte tel quel le modèle de l'amontL'amont ne bougera jamais (SI historique, API tierce)Faible à court terme, empoisonnant à long terme
Anti-corruption layerL'aval traduit le modèle amont dans le sienLe modèle amont est inadapté ou instableLe 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 :

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.

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. 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 :

Ce qui ne change pas nécessairement :

La séquence saine Découper d'abord en modules dans un monolithe. Vivre avec six mois. Extraire ensuite ce qui a prouvé son autonomie. L'inverse — extraire avant d'avoir vécu avec — est la façon la plus fiable de figer une mauvaise frontière dans du réseau.

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 :

TestRésultat
Le motnon User a un seul sens dans tout le système : le propriétaire d'une tâche. Aucune rupture de vocabulaire.
La transactionnon CreateTask vérifie l'existence du user dans la même transaction, et une FK ON DELETE CASCADE lie les tables.
L'équipenon Une seule équipe, un seul dépôt.
Le déploiementnon Un service, une base, une migration 001_initial_schema.py qui crée les deux tables.
Le glossairenon 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 :

  1. 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.
  2. 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.
  3. Ça prépare bien la suite. Le fait que task ne connaisse qu'un UserId rend 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 identityContexte task
User désigneun compte : identifiants, mot de passe haché, sessions, rôlesun propriétaire : un nom, un e-mail de contact
Cycle de viecréation, activation, verrouillage, suppression RGPDexiste tant qu'il a des tâches
Ce qui traverseUserId, 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#

Le piège du mot prestigieux « Bounded context » sonne mieux que « module ». C'est précisément pour ça qu'il faut se surveiller : le terme le plus flatteur est rarement le plus exact.

Pour aller plus loin#