Le Domain-Driven Design n’est ni une architecture à appliquer par principe, ni un luxe réservé aux très grandes plateformes. C’est une manière d’organiser le logiciel autour des décisions métier qui coûtent cher lorsqu’elles sont mal comprises. Dans une application de gestion, il devient utile quand un mot comme « validé », « disponible » ou « livré » recouvre plusieurs réalités, que ces différences modifient les règles et que plusieurs équipes doivent faire évoluer le produit sans se contredire. Le risque est de traiter cette ambiguïté comme un simple détail de code.
À l’inverse, une application qui enregistre quelques référentiels stables et exécute surtout des opérations CRUD n’a pas forcément besoin d’agrégats sophistiqués, d’événements de domaine ou de multiples couches. Copier tous les concepts du DDD dans ce contexte ajoute des fichiers, des interfaces et des traductions sans rendre une seule décision plus sûre. Le bon choix dépend donc moins de la taille du code que de la densité des règles, du coût d’une erreur et de la fréquence des changements.
La question pratique est simple : quelle partie du système mérite un modèle métier explicite, et quelle partie doit rester volontairement ordinaire ? Le développement d’une application métier résiliente commence par cette distinction. En pratique, le bon arbitrage consiste à réserver l’effort aux décisions critiques. Paradoxalement, un écran d’administration secondaire gagne souvent à conserver une implémentation directe. Cette approche sélective évite de transformer le DDD en dogme.
Nous allons partir de situations observables : vocabulaire contradictoire, invariants dispersés, transactions difficiles à expliquer, changements qui cassent plusieurs modules et incidents impossibles à diagnostiquer. L’objectif est de choisir un niveau de modélisation proportionné, dans une démarche de développement web sur mesure qui reste compréhensible par les équipes produit, techniques et opérationnelles.
Quand un événement de domaine clarifie vraiment le système
Distinguer le fait métier de la transaction technique
Un événement de domaine décrit un fait significatif déjà survenu : une commande a été acceptée, une réservation a expiré, un dossier est devenu facturable. Il ne doit pas servir à masquer une transaction nécessaire. Si la création de la facture et l’engagement comptable doivent réussir ensemble, les séparer artificiellement par un message asynchrone peut produire un état intermédiaire que personne ne sait réparer.
Avant de créer un événement, l’équipe décrit donc la règle, le moment où elle devient vraie et les données indispensables à sa vérification. Elle décide ensuite si les effets secondaires peuvent attendre et être rejoués. Envoyer une notification ou alimenter un moteur de recherche supporte généralement un traitement différé ; débiter un plafond de crédit exige souvent une garantie plus forte. Cette distinction donne du sens au diagramme de séquence et réduit les discussions abstraites sur les outils.
La promesse utilisateur associée à la règle d’invariant
Un invariant est une promesse que le système doit tenir à chaque changement d’état. Dans un outil de réservation, la quantité confirmée ne peut pas dépasser la capacité disponible. Dans un logiciel de financement, une décision approuvée doit conserver la version du barème utilisée. Ces règles ne sont pas de simples validations de formulaire : elles protègent une conséquence commerciale, juridique ou financière.
Le modèle de domaine apporte de la valeur quand il place cette promesse dans un endroit identifiable, testé avec le vocabulaire du métier. Si la règle est éparpillée entre un contrôleur, une procédure SQL, un traitement nocturne et un tableur du support, sa correction demande de retrouver toutes ses variantes. L’agrégat sert alors de frontière de cohérence : il accepte ou refuse la commande et produit un résultat explicable. Si aucune règle de cette nature n’existe, un service applicatif plus simple peut suffire.
Qui décide sur la dépendance technique pendant l’incident
Le DDD clarifie les responsabilités seulement si chaque frontière a un propriétaire. Pendant un incident, le métier doit pouvoir dire si l’état observé reste acceptable ; l’équipe produit arbitre la dégradation de service ; les développeurs établissent la cause et proposent la reprise ; l’exploitation contrôle l’exécution. Faire décider le DBA ou le SRE à la place du métier parce qu’il voit les données en premier crée une responsabilité de fait qui ne figure nulle part.
Une fiche courte suffit souvent : invariant concerné, source de vérité, commandes autorisées, événements émis et procédure de retour au nominal. Elle doit aussi distinguer la dépendance indispensable de l’adaptateur remplaçable. Une API de paiement peut être critique pour conclure la vente, tandis que le fournisseur retenu reste substituable. Ce découpage aide à choisir le mode dégradé sans demander à l’équipe d’infrastructure d’interpréter une règle commerciale sous pression.
Cartographier les modules et leur source de vérité
Une carte des modules utile ne recopie pas l’arborescence du dépôt. Elle indique ce que chaque module décide, quelles données il maîtrise et par quels contrats les autres peuvent lui parler. Par exemple, le module Catalogue décrit l’offre, le module Disponibilité promet une quantité et le module Commande engage le client. Si trois modules modifient directement la même table de stock, la frontière annoncée n’existe pas réellement.
L’état opposable doit être lisible sans reconstituer une conversation entre services. Un identifiant métier, une version, la date de la décision et l’origine de la commande permettent de comprendre pourquoi l’état courant existe. Le journal technique complète cette information, mais ne remplace pas le modèle. Cette discipline raccourcit le diagnostic et rend les migrations possibles : on sait quelle responsabilité déplacer, pas seulement quels fichiers copier.
Ordonner la frontière de domaine sans double effet
Le chemin nominal peut se résumer ainsi : une commande exprime l’intention, le domaine vérifie les invariants, l’agrégat change d’état, puis les événements décrivent les faits à propager. Cette séquence évite qu’un contrôleur HTTP prenne une décision différente d’un import CSV ou d’une tâche planifiée. Tous les points d’entrée passent par la même règle.
Les reprises imposent une précaution supplémentaire. Une commande dotée d’une clé d’idempotence ne doit pas appliquer deux fois le même mouvement. L’enregistrement de l’état et de l’événement à publier doit également résister à une panne entre les deux opérations ; un outbox transactionnel constitue souvent une réponse plus fiable qu’un envoi direct vers le bus. Ces choix ne relèvent pas d’une recherche de pureté architecturale : ils répondent à des scénarios précis de doublon, de délai ou de perte de message.
Tester les abstractions avant la mise en production
Vérifier que la règle reste visible dans les cas limites
La recette doit chercher le moment où l’abstraction cesse d’aider. Prenons un service générique de « changement de statut » partagé par dix objets. Il réduit la duplication apparente, mais il peut aussi cacher que l’annulation d’une commande rembourse le client alors que l’archivage d’un devis n’a aucun effet financier. Le test pertinent ne vérifie pas seulement que la méthode change une valeur ; il vérifie que chaque transition refuse les états interdits et déclenche les bons effets.
Il faut également provoquer la concurrence. Deux opérateurs confirment-ils la même ressource ? Un message ancien arrive-t-il après une décision plus récente ? Une reprise rejoue-t-elle un événement déjà consommé ? Les résultats attendus doivent être formulés avec le métier, puis contrôlés dans les tests d’intégration et dans la journalisation. Une abstraction qui oblige à ouvrir cinq classes avant de retrouver la règle mérite d’être simplifiée, même si elle respecte un patron de conception reconnu.
Enfin, la recette inclut une coupure de dépendance : bus indisponible, API partenaire en erreur ou base secondaire en retard. L’équipe vérifie ce qui reste autorisé, ce qui est mis en attente et comment le service revient au nominal. Si personne ne peut effectuer la reprise à partir du runbook, la frontière n’est pas prête pour la production.
Piloter avec le délai de diagnostic
Faire du délai de diagnostic un critère de décision
Le nombre de classes ou le respect d’un schéma hexagonal ne mesure pas la réussite. Un indicateur plus parlant est le temps nécessaire pour répondre à trois questions lors d’un incident : quelle règle a refusé ou accepté l’action, sur quelles données, et quelle reprise est sûre ? Si l’équipe retrouve la réponse dans un événement structuré et un état versionné, le modèle remplit son rôle. Si elle doit lire les logs de plusieurs services puis interroger une personne absente, le découpage reste théorique.
On peut suivre ce délai sur les exercices de recette et les incidents réels, avec le nombre de corrections touchant plusieurs modules, les régressions d’invariant et le temps moyen pour livrer une évolution métier. Ces mesures servent à arbitrer un investissement, pas à noter les équipes. Une baisse des régressions qui s’accompagne d’un temps de changement multiplié par trois peut signaler une architecture trop lourde.
Journaliser dans le modèle de domaine et préparer le rollback
Décrire entrées, sorties, dépendances et journalisation
La journalisation relie l’identifiant métier, la commande reçue, la version de l’agrégat, la décision prise et les événements produits. Le contrat de trace répartit les responsabilités entre le domaine et ses dépendances techniques. Il évite d’enregistrer des données personnelles inutiles ou des objets entiers impossibles à relire après une évolution de schéma. Les champs qui expliquent le résultat doivent rester stables et documentés.
Le rollback ne signifie pas toujours revenir à l’ancienne base. Après l’émission d’une facture ou l’envoi d’une commande à une dépendance fournisseur, le repli exige souvent une action compensatoire plutôt qu’un effacement de l’histoire. Le runbook doit nommer cette action : annuler, contrepasser, recréditer ou remettre en attente. Une formulation précise permet aux développeurs comme au support de comprendre les effets attendus.
Point de contrôle. La recette est exécutée avec des droits proches de la production. Un opérateur part d’un identifiant fonctionnel, retrouve la décision, distingue un rejet normal d’une panne et applique la procédure documentée. Le test se termine seulement lorsque l’état métier et les traitements asynchrones concordent.
Faire exécuter la recette par le DBA
Faire participer un DBA à la recette est utile pour vérifier les transactions, les verrous, les index et les conditions de restauration. Cela ne lui transfère pas la validation métier. Le scénario doit être lisible conjointement : le métier annonce le résultat attendu, le développeur explique la frontière, le DBA observe la cohérence des écritures et l’exploitation vérifie la supervision.
Un test révélateur consiste à interrompre le traitement après l’écriture principale mais avant la publication de l’événement. L’équipe confirme que l’outbox reprend le message sans doubler l’effet. Elle contrôle aussi qu’une migration de données conserve les invariants et qu’un retour de version applicative n’interprète pas mal un nouvel état. Ces essais exposent les véritables dépendances, là où une revue de diagramme peut laisser passer une hypothèse fragile.
Pour qui la méthode convient : le SRE
Le SRE bénéficie du modèle lorsqu’il peut relier une alerte technique à une conséquence métier. Une file de messages en retard ne justifie pas la même urgence si elle concerne l’indexation de recherche ou la confirmation d’une réservation. Les événements nommés, les niveaux de service et les procédures compensatoires donnent ce contexte sans exiger une connaissance intime du code.
La méthode convient surtout aux produits durables, riches en règles et exposés à plusieurs canaux ou équipes. Elle apporte moins à un prototype jetable, à un petit outil interne sans logique complexe ou à un référentiel très stable. Dans ces cas, conserver un code simple, bien testé et correctement découpé constitue souvent une meilleure décision d’architecture.
Erreurs fréquentes autour de l’événement de domaine
- Créer un événement pour chaque modification de champ. Seuls les faits qui intéressent une règle ou un autre composant méritent un contrat durable.
- Appeler “domaine” une couche de services sans décisions. Déplacer du code procédural derrière des interfaces ne protège aucun invariant.
- Partager les entités entre tous les modules. Le gain initial produit un couplage fort et empêche chaque contexte d’utiliser son propre vocabulaire.
- Confondre événement et transaction. L’asynchronisme n’efface ni la cohérence attendue ni le besoin de traiter les échecs partiels.
- Modéliser seul côté technique. Sans exemples fournis par les personnes qui prennent les décisions, le modèle reflète la base de données plutôt que le métier.
Arbitrer avec le module remplaçable
Une dépendance est remplaçable si le cœur métier connaît son besoin mais pas les détails du fournisseur. Le module de crédit demande par exemple une évaluation de solvabilité selon un contrat local ; un adaptateur traduit cette demande vers un prestataire. Ce principe permet de tester les décisions sans appeler le service distant et de préparer une migration sans réécrire les invariants.
Il ne faut pourtant pas créer une interface devant chaque classe. L’abstraction se justifie lorsque plusieurs implémentations sont plausibles, lorsqu’une dépendance externe change à son propre rythme ou lorsqu’un test métier a besoin de contrôler le résultat. Une base centrale et stable peut rester un détail assumé. L’arbitrage porte sur le coût futur crédible, pas sur la possibilité théorique de remplacer toute la pile.
Séquence opérationnelle : sécuriser l’événement de domaine et décider l’extension
D’abord, fermer le contrat de l’événement de domaine
Le contrat précise le nom du fait, sa signification, son producteur, ses consommateurs connus et la politique de compatibilité. Il ne transporte que les données nécessaires à la décision suivante. Un événement CommandeAcceptée peut contenir l’identifiant, la date, le montant engagé et la version du contrat ; il n’a pas besoin d’embarquer une copie complète du client, du catalogue et des paramètres de l’interface.
Chaque consommateur doit assumer sa propre reprise. Le producteur garantit qu’un fait enregistré sera publié ; il ne promet ni l’ordre global de tous les événements ni une livraison exactement une fois. Le consommateur mémorise les messages traités, refuse une version incompatible et expose son retard. Cette répartition explicite évite que la fiabilité repose sur une propriété magique du bus.
Le journal d’événements complète les tests d’invariant : il montre la version du contrat reçue, l’action exécutée et le résultat. Lorsqu’un consommateur est ajouté, l’équipe vérifie qu’il peut reconstruire son état depuis un point connu, sans relancer des effets externes dangereux. L’extension du périmètre dépend de cette preuve, pas seulement du succès du cas nominal.
- Nommer le fait au passé et écrire en une phrase ce qu’il garantit, sans référence au transport ni à l’écran qui l’a produit.
- Lister les données minimales, la version du schéma et les informations qui doivent être relues auprès de leur source.
- Tester doublon, retard, inversion de deux messages et indisponibilité d’un consommateur, puis documenter la reprise.
- Ouvrir le contrat à un nouveau module uniquement lorsque l’observabilité permet d’identifier le producteur, le message et l’effet appliqué.
Plan d’action : rendre l’usage proportionné du DDD dans une application métier vérifiable
Un statut « validé » qui signifie facturable pour la finance mais publiable pour les opérations justifie un travail de langage avant tout découpage technique. Les deux équipes nomment leurs événements, leurs invariants et le moment précis où elles se transmettent la responsabilité. Le DDD apporte de la valeur si cette clarification crée deux frontières testables ; il devient superflu si un simple renommage partagé suffit à lever l’ambiguïté.
Confronter deux cas concrets avant de généraliser
Cas A : une décision réellement ambiguë. Pour la finance, un dossier « validé » est facturable dès que les pièces sont conformes. Pour les opérations, il devient publiable seulement après un contrôle supplémentaire. Remplacer l’unique statut par deux décisions nommées — ConformitéAcceptée puis PublicationAutorisée — retire une ambiguïté du code, des écrans et des échanges entre équipes. Chaque étape possède désormais ses règles et son responsable.
Cas B : un référentiel stable. Une liste de types de documents comporte un libellé, un ordre et un indicateur d’activation. Elle évolue rarement et aucune règle critique ne dépend de sa modification immédiate. L’entourer d’une fabrique, d’un agrégat, d’un dépôt abstrait et d’événements spécialisés ne clarifie rien. Une table administrable, un service simple, une validation et des tests offrent un meilleur rapport entre robustesse et maintenance.
Ces deux cas peuvent coexister dans la même application. L’équipe n’a pas à choisir entre « tout DDD » et « aucun DDD ». Elle concentre les ateliers de modélisation, les types métier et les tests approfondis sur le premier cas. Le second suit les conventions habituelles du framework. Cette asymétrie est saine : elle rend visible la différence de criticité au lieu de l’effacer sous une architecture uniforme.
Relier le contrat technique à la responsabilité métier
Un atelier utile part d’exemples et de contre-exemples. Qu’est-ce qui autorise la publication ? Que se passe-t-il si la conformité expire entre deux étapes ? Qui peut forcer la décision et avec quelle trace ? Les réponses font émerger commandes, invariants et événements. Le code vient ensuite matérialiser ces choix ; il ne remplace pas la discussion.
Pour chaque règle importante, une personne du métier valide le vocabulaire et les cas limites. Un responsable technique vérifie que tous les canaux passent par la même décision. L’exploitation confirme qu’elle peut reconnaître l’état et appliquer la reprise. Cette chaîne de responsabilité reste légère : une page de décision, quelques scénarios exécutables et un tableau de bord ciblé valent mieux qu’un document exhaustif jamais relu.
Un modèle plus petit exprime parfois davantage de métier. Une classe PlafondDeCrédit qui refuse explicitement un dépassement est plus informative qu’une hiérarchie générique de validateurs. La qualité ne se mesure donc pas au nombre de concepts tactiques employés, mais à la capacité du code à préserver et expliquer les décisions importantes.
Décider avec une séquence courte et opposable
- D’abord, nommer. Repérer les décisions coûteuses, les termes ambigus, les sources de vérité et les dépendances qui changent à des rythmes différents.
- Ensuite, choisir. Retenir un seul flux où les incidents ou les évolutions révèlent déjà une faiblesse de modèle.
- Puis, tester. Écrire le cas accepté, le refus, la concurrence et la reprise avant de retenir les abstractions.
- Après deux lots, comparer. Contrôler les régressions, le délai de diagnostic, le temps de développement et la charge de support par rapport à la situation initiale.
- Enfin, décider. Conserver le modèle s’il simplifie les décisions ; revenir à une conception plus directe s’il ajoute surtout des traductions et des fichiers.
Pour qui cette méthode est utile
Cette méthode s’adresse aux responsables produit, experts métier, développeurs et architectes qui font évoluer une application sur plusieurs années. Elle est particulièrement utile lorsque plusieurs équipes interprètent une même décision, qu’un changement traverse différents canaux ou que la reprise dépend encore de la mémoire d’une personne.
Elle doit rester proportionnée. Un changement local, réversible et bien couvert par les tests ne justifie pas un comité ni une nouvelle couche. En revanche, les mouvements d’argent, les droits d’accès, les engagements client, les stocks rares et les données réglementées méritent un invariant explicite, une responsabilité identifiée et une trace durable.
Erreurs fréquentes à éliminer
La première erreur consiste à reproduire la structure d’un ouvrage sans la confronter aux cas du produit. La deuxième impose un vocabulaire technique aux utilisateurs au lieu de partir de leurs décisions. La troisième valide uniquement le chemin heureux et découvre trop tard que la concurrence, le rejeu ou le retour arrière contredisent le modèle.
- Refuser une règle dont personne ne peut citer la source, le responsable et un exemple contradictoire.
- Différer l’extension si la procédure de reprise n’a pas été exécutée avec les outils disponibles en production.
- Supprimer une abstraction si elle ne protège aucun invariant, ne sépare aucun rythme de changement et n’isole aucune dépendance.
- Conserver la décision d’architecture, sa date et ses limites afin qu’un nouveau développeur comprenne pourquoi le périmètre a été retenu.
Guides complémentaires pour fiabiliser l’événement de domaine
Relier le produit au premier verdict de run
Une frontière métier ne devient exploitable que si ses décisions apparaissent dans les signaux de production. Le guide d’observabilité des workflows métier détaille la manière de relier les identifiants fonctionnels, les étapes du traitement et les alertes sans exposer inutilement les données.
Vérifier les tests, le mode dégradé et la maintenance
Le guide performance, monitoring et observabilité complète cette démarche avec les mesures de latence, d’erreur et de saturation. Il aide à distinguer un refus normal du domaine d’une défaillance de l’infrastructure.
- Relire le nom, la garantie, le producteur et la politique de compatibilité de chaque événement de domaine.
- Faire exécuter par le support un diagnostic à partir d’un identifiant métier et de la documentation disponible.
- Décider l’extension selon les erreurs de concurrence observées, le coût de maintenance et la réussite de la reprise.
Conclusion : choisir un DDD proportionné au risque métier
Le DDD mérite sa place lorsqu’il rend une décision métier plus claire, protège un invariant coûteux et sépare des responsabilités qui évoluent différemment. Il devient trop lourd quand les abstractions précèdent les problèmes, que le vocabulaire reste celui de la base de données ou que chaque petite fonctionnalité traverse une succession de couches sans bénéfice observable.
La meilleure stratégie consiste à commencer par un flux conflictuel, construire le modèle à partir d’exemples réels et vérifier son effet après quelques évolutions. Le statut ambigu et le référentiel stable ne demandent pas le même investissement. Accepter cette différence permet de conserver un cœur riche là où il est nécessaire et un code direct partout ailleurs.
Une architecture proportionnée laisse enfin une trace exploitable : la règle, son responsable, les conditions de reprise et les raisons du découpage. Elle ne promet pas l’absence d’incident, mais elle évite de reconstruire le sens du système au moment le plus difficile.
Pour inscrire cette approche dans une trajectoire de développement web sur mesure, notre équipe peut cadrer les scénarios, auditer les frontières existantes et construire un premier module vérifiable avec les personnes qui assureront son évolution et son exploitation.