Modéliser avant de générer : le schéma comme contrat avec l'agent
Une application de gestion des congés, décrite à un agent en trois paragraphes de français précis, relus deux fois. Quelques minutes plus tard, le projet existe : entités, migrations, endpoints, tests, tout compile et tout se tient. Puis, à la lecture du modèle de données, ce détail : un salarié a un manager. Un seul. Alors que dans cette organisation il y en a deux, le hiérarchique et le fonctionnel, et que les deux valident les absences. Personne n’avait écrit le contraire. Le mot « son manager », au singulier, avait suffi à trancher une question que la description ne posait même pas. Et cette cardinalité est désormais dans la migration, dans les entités, dans les DTO, dans les tests qui la confirment.
Ce n’est pas une défaillance du modèle. C’est une défaillance de la spécification, et elle était déjà là avant que l’agent ne s’en saisisse. Le français autorise à ne pas décider. Un diagramme entité-association, non : on ne dessine pas une relation sans dire combien d’un côté et combien de l’autre.
Le texte laisse des trous, le schéma les rend visibles
Une description en prose peut être parfaitement claire à la lecture et ne rien dire de la cardinalité entre deux entités, du sens réel d’une dépendance, ou de ce qui arrive quand deux événements se présentent en même temps. À la relecture, on ne voit pas le trou, parce que notre tête le comble automatiquement avec ce qu’on sait du métier, de l’entreprise, des conversations de la semaine dernière. Le trou n’existe que pour celui qui n’a pas ce savoir.
L’agent n’a pas ce savoir. Il comble avec la statistique de son entraînement, c’est-à-dire avec l’application de gestion de congés la plus banale du corpus. Le résultat est plausible, et c’est précisément le problème : plausible, ça ressemble à juste. Une erreur grossière se voit à la première exécution ; une cardinalité fausse se voit six mois plus tard, quand le métier demande pourquoi les validations fonctionnelles n’apparaissent nulle part.
Un formalisme graphique n’a pas cette indulgence. Il ne permet pas de tracer une flèche sans dire qui appelle qui, ni de poser une relation sans en fixer la multiplicité et l’optionalité. Il force la décision au moment où elle coûte le moins cher. C’est pénible, ça l’a toujours été, et c’est exactement là qu’est la valeur du geste : la gêne qu’on ressent en dessinant est celle qu’on s’épargne trois jours plus tard. Ce que la modélisation apporte n’a pas changé avec l’IA. Ce qui a changé, c’est à qui elle s’adresse, et ce que son absence coûte.
La même fonctionnalité, décrite deux fois
Prenons le cas le plus banal du domaine, la validation d’une demande, et écrivons-le comme on l’écrit spontanément dans un ticket.
Une demande de congé est soumise par le salarié, puis validée par sa hiérarchie. Une fois validée, elle est transmise au SIRH. Le salarié peut annuler sa demande.
Quatre phrases, rien de choquant, et n’importe quel relecteur humain les validerait. Voici pourtant ce qu’elles ne disent pas, et que l’agent devra donc décider seul.
« Sa hiérarchie » désigne combien de personnes ? Et s’il y en a plusieurs, faut-il l’accord de toutes, la majorité, ou le premier qui répond ? « Une fois validée » signifie-t-il que la transmission au SIRH se fait dans la même transaction, ou plus tard, de façon asynchrone ? « Peut annuler » vaut-il à tout moment, y compris après validation, y compris après le début du congé ? Et rien, absolument rien, ne dit ce qui doit se passer si le SIRH est indisponible au moment de l’appel.
L’agent tranchera les quatre points, en silence, dans le sens statistiquement le plus courant : un validateur unique, un appel synchrone au SIRH depuis le contrôleur, une annulation possible tant que le statut n’est pas validé, et aucune reprise sur erreur. Dans notre organisation, trois de ces quatre choix sont faux. Aucun ne produira la moindre erreur au démarrage, et deux d’entre eux ne se manifesteront que le jour où le SIRH tombera.
Le point important n’est pas que l’agent se soit trompé. C’est qu’il n’avait pas les moyens de savoir qu’il y avait quelque chose à demander. Une question implicite ne se pose pas, elle se comble. Modéliser, c’est rendre ces questions visibles avant qu’elles ne soient comblées à notre place.
Le destinataire du diagramme a changé
Un schéma servait à aligner des humains. Quelqu’un le projetait en réunion, on discutait une heure, chacun repartait avec une compréhension à peu près commune, et le diagramme finissait sa vie dans un wiki que plus personne n’ouvrait. La valeur était dans la conversation, pas dans l’artefact, ce qui explique la réputation durable de la modélisation : une cérémonie coûteuse dont le produit se périme aussitôt.
Cette économie s’inverse. Le diagramme n’est plus seulement lu par des humains distraits, il est consommé par un agent qui va littéralement écrire le code d’après. Un ERD cesse d’être l’illustration de ce que fera l’équipe pour devenir l’entrée dont sortent les migrations.
C’est le context engineering qui rend ce basculement intéressant. Puisque la qualité d’une génération dépend d’abord de ce que le modèle a sous les yeux, la vraie question devient : quelle forme d’information verrouille le plus de décisions par token consommé. Vingt lignes de Mermaid tranchent plus de questions structurelles que deux mille mots de description, pour une fraction du contexte.
Il y a un effet secondaire agréable à cela, et il vaut la peine d’être noté. Un schéma dense reste utile pendant toute la durée du projet, alors qu’une longue description en prose est relue une fois puis abandonnée. On paie le coût de rédaction une seule fois, et on le réamortit à chaque tâche confiée à un agent, ce qui n’a jamais été le cas des spécifications d’antan.
Ce que chaque niveau verrouille
Il ne s’agit pas de tout modéliser. Quatre schémas suffisent pour la plupart des applications de gestion, à condition de choisir ceux qui répondent aux questions que l’agent va se poser en silence, et sur lesquelles il se trompera sans jamais le signaler.
Les frontières : C4
Le niveau conteneur du modèle C4 dit ce qui se déploie séparément, qui parle à qui, dans quel sens, par quel protocole. C’est ce qui empêche un agent de brancher directement l’interface sur la base parce que c’était le cas dans un exemple qu’il a vu quelque part.
flowchart LR
U[Salarié] --> W[Web SPA]
M[Manager] --> W
W -->|HTTPS/JSON| A[API Congés]
A -->|SQL| D[(PostgreSQL)]
A -->|AMQP, publie| B[[Bus de messages]]
B --> N[Worker Notifications]
B --> S[Worker Synchro SIRH]
S -->|REST| P[SIRH externe]
Neuf lignes, et l’agent sait déjà quatre choses qu’il aurait inventées autrement : les notifications sont asynchrones, la synchronisation SIRH aussi, le SIRH est hors du périmètre transactionnel de l’API, et la SPA n’a aucun accès direct aux données. Le sens des flèches n’est pas décoratif non plus : A -->|AMQP, publie| B dit que l’API publie et ne consomme pas, ce qui interdit à l’agent d’y placer un abonnement.
L’erreur classique, à ce niveau, est de descendre trop bas. Un C4 de niveau conteneur qui déborde sur les classes internes de l’API n’apporte rien à l’agent, qui lira le code de toute façon, et devient faux au premier refactoring. On s’arrête à ce qui se déploie et à ce qui communique.
Les données : ERD
C’est le schéma le plus rentable, parce que ses erreurs sont les plus coûteuses à défaire. Une cardinalité fausse se propage dans les migrations, les index, les requêtes, les contrats d’API et les écrans. Six mois plus tard, la corriger est un chantier.
Voici ce qu’un agent déduit spontanément de la description en prose du début.
erDiagram
SALARIE ||--o{ DEMANDE_CONGE : depose
SALARIE ||--o{ SALARIE : encadre
DEMANDE_CONGE ||--o| VALIDATION : possede
Ce modèle est cohérent, il compile, il produit des migrations propres, et il est faux sur les deux points qui comptent. Voici le modèle voulu.
erDiagram
SALARIE ||--o{ DEMANDE_CONGE : depose
SALARIE }o--|| SERVICE : rattache_a
SALARIE }o--o{ SALARIE : encadre_par
DEMANDE_CONGE ||--|{ VALIDATION : requiert
VALIDATION }o--|| SALARIE : validee_par
DEMANDE_CONGE }o--|| TYPE_ABSENCE : releve_de
Deux caractères séparent les deux versions sur la ligne de l’encadrement, et ces deux caractères valent une table de liaison plutôt qu’une clé étrangère. La notation en patte de corbeau se lit d’ailleurs très simplement, et il vaut la peine de la connaître puisque c’est elle qui porte l’information : || signifie exactement un, o| zéro ou un, }o zéro ou plusieurs, }| un ou plusieurs. Le ||--|{ sur la ligne des validations dit donc qu’une demande exige au minimum une validation, ce qui interdit d’emblée le modèle où la validation serait une simple colonne nullable sur la demande.
Aucune phrase française n’aurait produit ce résultat de façon aussi fiable. C’est aussi pour cela que je préfère montrer l’ERD à l’agent plutôt que de lui décrire les tables : le format porte des contraintes que la prose ne sait exprimer qu’au prix de circonlocutions que personne ne relit.
Le cycle de vie : diagramme d’états
Un CRUD se devine. Un cycle de vie ne se devine pas, et c’est là que la génération invente le plus. Un diagramme d’états dit quelles transitions existent, mais surtout lesquelles n’existent pas, et l’absence de flèche est une information à part entière.
stateDiagram-v2
[*] --> Brouillon
Brouillon --> EnAttente : soumettre
Brouillon --> [*] : supprimer
EnAttente --> Validee : accord des deux managers
EnAttente --> Refusee : refus de l'un des deux
EnAttente --> Annulee : retrait par le salarié
Validee --> Annulee : annulation, préavis de 48h
Validee --> EnCours : date de début atteinte
EnCours --> Soldee : date de fin atteinte
Refusee --> [*]
Soldee --> [*]
Ce schéma répond d’un coup à trois des quatre questions que la prose laissait ouvertes. Il dit qu’il faut l’accord des deux managers et que le refus d’un seul suffit. Il dit qu’une annulation reste possible après validation, mais sous condition de préavis. Et il dit, par ce qu’il ne contient pas, qu’une demande refusée ne revient jamais en attente et qu’un congé en cours ne s’annule plus. Un agent qui a ce diagramme sous les yeux n’écrira pas la méthode Reactiver() qu’il aurait ajoutée par prévenance.
En pratique, je demande aussi que la machine à états soit implémentée explicitement, avec une table de transitions autorisées, plutôt que disséminée dans des if répartis sur trois services. Le diagramme devient alors littéralement traduisible en code, et l’écart entre les deux se voit au premier coup d’œil.
La chorégraphie : diagramme de séquence
Reste ce qui se passe entre les composants, dans quel ordre, et où se ferme la transaction. C’est le niveau le plus souvent négligé, et celui où les erreurs sont les plus pénibles à diagnostiquer, parce qu’elles ne se manifestent qu’en charge ou en panne.
sequenceDiagram
participant W as Web SPA
participant A as API Congés
participant D as PostgreSQL
participant B as Bus AMQP
participant S as Worker SIRH
W->>A: POST /demandes/{id}/validations
A->>D: insérer la validation
alt les deux validations sont présentes
A->>D: statut = Validee (même transaction)
A->>D: insérer l'événement CongeValide (outbox)
end
A-->>W: 200, statut courant
D-->>B: relais de l'outbox
B->>S: CongeValide
S->>S: appel SIRH, rejouable
Trois décisions structurantes sont verrouillées ici, et aucune ne se lisait dans les quatre phrases initiales. La transaction se ferme avant tout appel externe. L’événement passe par une table d’attente plutôt que d’être publié directement, ce qui garantit qu’on ne perd pas un message si le bus est indisponible. Et la reprise sur erreur vit dans le worker, pas dans le contrôleur, ce qui évite que la réponse HTTP dépende de la disponibilité d’un système tiers.
Un agent à qui l’on donne ce schéma implémente une outbox. Le même agent, sans ce schéma, place un await _sirh.SynchroniserAsync(...) juste après le SaveChanges, et le code passera tous les tests, jusqu’au premier incident.
Ce qu’aucun de ces schémas ne porte
Il faut être honnête sur les limites de l’exercice, sous peine de croire le système spécifié alors qu’il ne l’est qu’à moitié. Les formalismes graphiques décrivent des structures et des enchaînements. Ils ne décrivent pas les règles de calcul, et c’est souvent là que se cache la complexité réelle du métier.
Comment décompte-t-on les jours quand un congé chevauche un jour férié ? Une demi-journée compte-t-elle pour 0,5 ou arrondit-on à l’unité supérieure en fin de mois ? Que vaut le solde d’un salarié entré en cours d’année ? Aucun ERD, aucun diagramme d’états ne répondra jamais à ces questions. Elles relèvent d’un autre registre, celui des règles écrites et des exemples chiffrés, et il faut les fournir séparément, sous forme de tableau de cas ou de tests d’acceptation. Le schéma cadre la structure, il ne dispense pas de spécifier le calcul.
Où vivent ces schémas, et comment ils entrent dans le contexte
Le format compte, et il n’est pas négociable : du texte. Mermaid, PlantUML ou Structurizr, mais pas un outil de dessin. Ce n’est pas une préférence esthétique, c’est ce qui rend le schéma utilisable dans la boucle.
Un diagramme en texte se versionne, donc il se diffe : un changement de cardinalité apparaît dans la pull request, en trois caractères parfaitement visibles, au lieu d’être enfoui dans une migration que personne ne relit ligne à ligne. Il entre dans le contexte de l’agent tel quel, sans capture d’écran ni conversion. Et l’agent peut le produire et le modifier, ce qu’il ne fera jamais d’un PNG exporté d’un outil propriétaire.
En pratique, je range ces fichiers à côté du code qu’ils gouvernent, un fichier par sujet, jamais un gros document unique.
docs/model/
01-contexte.mmd C4 niveau 1, qui utilise le système
02-conteneurs.mmd C4 niveau 2, ce qui se déploie
10-donnees.mmd ERD du domaine Congés
20-cycle-demande.mmd machine à états de la demande
30-validation.mmd séquence de la validation
README.md index, et règles de mise à jour
Le découpage n’est pas cosmétique. Il permet de n’injecter dans le contexte que le ou les schémas pertinents pour la tâche en cours, ce qui rejoint directement la discipline du budget de tokens : une tâche sur le calcul des soldes n’a pas besoin du C4, une tâche sur l’infrastructure n’a pas besoin de la machine à états.
Reste à s’assurer que l’agent les lise. Un dossier que personne ne référence n’existe pas, et compter sur le hasard d’une recherche de fichiers est une stratégie médiocre. La bonne pratique est de pointer ces schémas depuis le fichier d’instructions du dépôt, dans l’esprit des fichiers de règles destinés aux agents, avec une consigne explicite sur leur autorité.
## Modèle de référence
Les schémas de `docs/model/` font foi. Avant toute modification du domaine
Congés, lire `10-donnees.mmd` et `20-cycle-demande.mmd`.
Toute divergence entre le code et ces schémas est un bug : signaler l'écart
et proposer une mise à jour du schéma dans la même pull request, ne jamais
l'ignorer silencieusement.
La dernière phrase est celle qui fait la différence. Sans elle, un agent confronté à une contradiction entre le code et le schéma choisira le code, parce que le code est plus concret, et la divergence s’installera sans que personne ne l’apprenne.
Le schéma sert deux fois
Le second usage est celui qu’on oublie, alors qu’il est presque plus utile que le premier. Une fois le code produit, on demande à l’agent de reconstruire le diagramme à partir de ce qu’il vient d’écrire, puis on compare avec le schéma de référence.
Lis les entités et la configuration EF Core de
src/Conges/Domain. Produis un ERD Mermaid strictement dérivé du code, sans consulterdocs/model/10-donnees.mmdet sans rien inférer des noms. Liste ensuite les écarts avec ce fichier.
La consigne de ne pas consulter la référence est essentielle : sinon l’agent aligne son résultat sur ce qu’il sait être attendu et la vérification ne vaut plus rien. On veut une observation, pas une confirmation.
S’ils divergent, soit l’implémentation s’est écartée de l’intention, soit le modèle était incomplet et il faut le compléter. Dans les deux cas, on a appris quelque chose que les tests ne disaient pas. Les tests unitaires valident que le code fait ce qu’il a été codé pour faire ; ils ne disent rien de l’écart entre ce qui a été codé et ce qui avait été conçu. Comparer deux diagrammes attrape précisément cette classe d’erreurs, celle qui passe toutes les suites au vert.
C’est aussi la forme la plus directe de la revue telle que je la conçois désormais, valider une décision plutôt que relire des lignes : deux schémas côte à côte se comparent en trente secondes, là où la même vérification demanderait de lire quinze fichiers.
Et pour la partie mécanique, celle qui ne demande aucun jugement, l’automatisation est immédiate.
- name: Vérifier la dérive du modèle de données
run: |
dotnet run --project tools/ErdFromEf -- --out /tmp/erd-code.mmd
diff -u docs/model/10-donnees.mmd /tmp/erd-code.mmd
Un ERD régénéré depuis les entités et comparé à la référence, et la dérive devient impossible à ignorer. Le job échoue, l’auteur de la pull request doit trancher explicitement : soit le code est faux, soit le schéma doit évoluer, mais il ne peut plus laisser les deux se contredire en silence.
Trois pièges
Le premier est le retour de la sur-modélisation. UML est mort d’avoir voulu tout décrire avant d’écrire une ligne, et rien n’oblige à répéter l’erreur au motif que le destinataire a changé. On modélise ce qui est coûteux à défaire : les données, les frontières, les cycles de vie, les séquences distribuées. Le reste, l’agent le déduira très bien du code existant, et le modéliser à l’avance revient à écrire deux fois la même chose pour ensuite les désynchroniser. Le bon test est simple : si l’erreur se corrige en une heure une fois découverte, elle ne mérite pas un schéma.
Le deuxième est plus insidieux. Un schéma faux est pire que pas de schéma, parce qu’il est consommé avec autorité. Un diagramme obsolète glissé dans le contexte, c’est une hallucination que vous avez fournie vous-même, et l’agent n’a aucun moyen de la mettre en doute. C’est le prolongement direct de ce qui vaut pour la sécurité des agents : tout ce qui entre dans le contexte est traité comme vrai, donc tout ce qui entre dans le contexte engage votre responsabilité. D’où la vérification en intégration continue, qui n’est pas un luxe mais la condition pour que le dispositif tienne dans le temps.
Le troisième est le plus discret, et je l’ai vu se retourner contre des équipes bien intentionnées. Un schéma trop détaillé sur les points qui ne comptent pas fait perdre à l’agent la hiérarchie de ce qui est structurant. Si le diagramme de séquence descend jusqu’aux appels de logging, l’agent traitera la ligne de log avec le même sérieux que la frontière de transaction, parce que rien dans le format ne dit laquelle des deux est négociable. Le schéma doit porter les invariants, pas la totalité de l’implémentation, et cette sélection est exactement le travail de conception.
Le flou ne coûte plus une réunion
L’IA n’a pas rendu la conception inutile, elle a rendu son absence beaucoup plus chère. Avant, une ambiguïté dans une spécification coûtait une réunion, un aller-retour, une correction avant que le code n’existe. Aujourd’hui elle coûte quinze fichiers parfaitement cohérents entre eux et faux ensemble, produits en quatre minutes, avec des tests qui confirment l’erreur.
C’est le paradoxe de la vitesse sous sa forme la plus concrète. La demi-heure passée à dessiner un ERD est le moment du projet où l’on a le plus l’impression de ne rien produire, et c’est celui qui rapporte le plus. Ce n’est pas de la cérémonie, c’est du cadrage, et le cadrage est devenu la partie du travail que l’agent ne peut pas faire à votre place, faute de connaître l’organisation pour laquelle il code.
Il faut d’ailleurs mesurer le renversement qui s’est produit. Pendant vingt ans, la modélisation a perdu du terrain parce que son coût était immédiat et son bénéfice différé, incertain, difficile à défendre en réunion de planification. Ce calcul ne tient plus : le bénéfice est désormais immédiat lui aussi, il se constate dès la première génération, sur du code qu’on n’aura pas à jeter. Ce qui était une discipline d’ingénieur consciencieux est devenu un réflexe rentable dès la première heure.
Un agent ne devine pas votre métier. Il devine le métier moyen. Le schéma reste la façon la plus économique de lui dire en quoi le vôtre en diffère.
// À lire ensuite