Aller au contenu principal

ADR-014 — DMV Core : Bounded Contexts et Monolithe Modulaire

Statut

Accepted

Date

2026-07-22

Décideurs

Équipe Architecture DMV

Impact

DMV Core, API, tous les Bounded Contexts internes, toute application consommant DMV Core

Contexte

ADR-000 a défini DMV Core comme le cœur de la plateforme DMV, propriétaire des services partagés que les applications (DMV Public, Workspace, AssoSuite, PlayLoop, Coach, futures applications) consomment sans jamais dupliquer.

Cette ADR ne revient pas sur cette décision. Elle définit l'architecture interne de DMV Core lui-même : comment il s'organise pour rester compréhensible, maintenable par plusieurs équipes en parallèle, et pour ne jamais devenir le monolithe fonctionnel qu'ADR-000 cherche précisément à éviter.

Elle ne décrit aucune arborescence de dossiers et ne déclenche aucune refactorisation. Elle fixe des principes, dans l'esprit de 27-architecture-governance.md : une décision structurante avant le développement, jamais l'inverse.

Relation avec l'architecture déjà actée

Cette ADR ne redéfinit ni les Engines, ni les Platform Services, ni l'Infrastructure, déjà posés dans 26-platform-architecture.md (et rappelés par ADR-011). Elle n'ajoute pas non plus de cinquième couche à ce modèle.

Le principe déjà établi par ADR-000 — DMV Core répond à où vit une responsabilité, la distinction Engines / Platform Services / Infrastructure répond à quelle est la nature de cette responsabilité, deux axes orthogonaux — s'étend d'un niveau : le Bounded Context, unité d'organisation interne de DMV Core définie par cette ADR, est lui aussi orthogonal à ce modèle. Un Bounded Context peut porter la logique de décision d'un Engine, l'exécution d'un Platform Service, ou les deux à la fois selon les capacités qu'il expose — ce que cette ADR fixe, c'est qui est propriétaire des données et des décisions à l'intérieur de DMV Core, pas la nature de chaque responsabilité, déjà tranchée ailleurs.

Décision

DMV Core adopte un monolithe modulaire, organisé en Bounded Contexts.

Un seul déploiement peut être conservé. Les frontières internes entre Bounded Contexts sont traitées avec la même rigueur que des frontières de services réseau. L'extractibilité future d'un Bounded Context en service séparé est une conséquence possible de cette rigueur, jamais l'objectif initial de conception — DMV Core n'est pas construit en vue d'une migration vers les microservices, il est construit pour rester compréhensible et évolutif ; le fait qu'une extraction future reste réaliste en découle, sans en être le but.


1. Unité d'ownership

Le Bounded Context est l'unité d'ownership métier de DMV Core.

Chaque Bounded Context possède :

  • son vocabulaire ;
  • ses règles ;
  • ses décisions ;
  • ses données sources ;
  • ses contrats publiés ;
  • ses événements métier.

Le module est sa représentation physique dans le code. Une donnée possède exactement un Bounded Context propriétaire, seul habilité à la modifier.

2. Ownership des décisions

Un Bounded Context est propriétaire de ses décisions, jamais des conséquences externes de ses décisions.

Exemple : Publication décide qu'une publication est publiée et émet un fait métier. Il ne décide pas lui-même d'envoyer une notification, de mettre à jour la recherche, d'envoyer un email, ou d'alimenter un autre produit. Chaque contexte consommateur reste propriétaire de sa propre réaction et de sa propre fiabilité — la fiabilité d'une réaction (nouvelle tentative, garantie de livraison) est la responsabilité du contexte qui réagit, jamais de celui qui a émis le fait.

3. Processus multi-contextes

Ce principe se nuance lorsqu'un résultat métier garanti nécessite la collaboration de plusieurs Bounded Contexts, dans un ordre défini, avec une gestion explicite des échecs.

Un tel processus (Process Manager / Saga) doit alors être :

  • explicite ;
  • nommé ;
  • propriétaire uniquement de l'orchestration — jamais des règles internes de chaque contexte qu'il orchestre ;
  • responsable de l'ordre, du suivi, des échecs et des compensations.

Il ne doit jamais être absorbé implicitement par l'un des contextes participants. Une Saga qui orchestre l'activation d'un acteur ne décide jamais, à la place d'Actor, des règles d'activation elles-mêmes — elle appelle Actor pour cette décision et enchaîne les étapes suivantes selon le résultat.

4. Dépendances

Aucun Bounded Context ne dépend :

  • des modèles internes d'un autre ;
  • de son ORM ;
  • de ses tables privées ;
  • de ses règles métier internes.

Les interactions passent exclusivement par des contrats publiés : service applicatif synchrone, commande, événement, ou modèle de lecture explicitement exposé.

Cette règle porte sur les contrats et l'ownership, pas sur une classification rigide des Bounded Contexts en « cœur » et « support » qui déterminerait par avance qui a le droit de dépendre de qui — une telle hiérarchie représenterait mal certains besoins métier réels et deviendrait elle-même une source de friction. Le critère qui gouverne une dépendance autorisée est unique : cible-t-elle un contrat explicitement publié par le contexte propriétaire, ou cherche-t-elle à contourner son ownership ?

5. Communication

Trois mécanismes, chacun pour un usage précis.

Appel synchrone — pour obtenir une réponse immédiate nécessaire à une décision locale. Il cible un contrat applicatif publié, jamais le modèle interne du contexte appelé.

Commande — pour demander explicitement à un autre contexte d'exécuter une capacité dont il reste propriétaire. Le contrat est défini et accepté par le contexte récepteur, jamais imposé par l'émetteur.

Événement — pour publier un fait déjà décidé et committé, sans connaître ses consommateurs. C'est le mécanisme privilégié pour les conséquences découplées (principe 2).

6. Transactions et cohérence

  • Une transaction métier ne couvre qu'un seul Bounded Context, et uniquement les données lui appartenant.
  • Tout échange entre contextes implique une cohérence éventuellement différée.
  • Aucune transaction distribuée n'est supposée exister.
  • Une lecture synchrone préalable peut informer une décision locale, mais les écritures de deux contextes ne sont jamais committées ensemble.

Les interfaces et les consommateurs doivent tolérer explicitement les fenêtres de cohérence éventuelle plutôt que de supposer une cohérence immédiate entre contextes.

7. Transactional Outbox

Le fait métier et l'intention de publier son événement sont enregistrés dans la même transaction locale.

L'événement n'est jamais envoyé avant le commit du fait qu'il décrit. La publication effective intervient après le commit, avec une garantie de livraison au moins une fois.

Le choix technique du mécanisme de publication (répartiteur en mémoire, file de messages, ou autre) est un détail d'implémentation, non tranché par cette ADR.

8. Idempotence et ordre

Tout consommateur d'événement doit être idempotent : la redélivrance d'un même événement ne doit jamais produire une deuxième conséquence métier.

L'ordre global entre événements de contextes différents n'est jamais supposé. Lorsqu'un ordre est indispensable à un processus, celui-ci doit être porté explicitement par un Process Manager (principe 3) ou par une règle de séquencement documentée — jamais par une hypothèse implicite sur l'ordre de livraison.

9. Événements immuables et versionnés

Un événement publié est un fait immuable.

  • Ne jamais modifier rétrospectivement son sens.
  • Privilégier l'ajout de champs optionnels.
  • Ne jamais retirer ou redéfinir silencieusement un champ existant.
  • Introduire une nouvelle version en cas de rupture, avec une période de coexistence entre versions.
  • Exiger de tout consommateur qu'il ignore les champs qu'il ne reconnaît pas.

10. Pas d'Event Sourcing implicite

DMV Core n'adopte pas l'Event Sourcing par cette ADR. Les événements ne constituent pas, par défaut, la source de vérité permettant de reconstruire intégralement l'état d'un Bounded Context.

La durée de conservation des événements dépend des besoins opérationnels, de la reprise après incident, de l'audit, des obligations légales, et des règles de confidentialité et de minimisation applicables à DMV — elle n'est pas fixée par cette ADR.

Pour initialiser un nouveau Bounded Context, un backfill depuis les sources de vérité des contextes existants peut être réalisé avant que ce nouveau contexte ne commence à consommer les événements futurs. La conservation illimitée des événements n'est en aucun cas présentée ici comme un principe architectural par défaut.

11. Propriété et lecture des données

L'écriture croisée est strictement interdite : un Bounded Context ne modifie jamais les données appartenant à un autre.

Pour les lectures inter-contextes, les mécanismes autorisés sont : un contrat de lecture publié, une projection locale entretenue par le contexte consommateur, ou un modèle de lecture composé et explicitement gouverné.

Une jointure directe sur les tables internes d'un autre contexte, ou l'import direct de son modèle ORM, est interdit. Un modèle de lecture composé n'acquiert jamais l'ownership des données qu'il expose — il reste un assemblage en lecture, jamais une nouvelle source de vérité.

12. DMV Core et ses adaptateurs

DMV Core n'est pas l'API.

REST n'est qu'un adaptateur parmi d'autres. GraphQL, CLI, Scheduler, Queue, MCP, une IA agissant directement sur DMV Core, ou toute interface future, peuvent invoquer les mêmes capacités par leurs propres adaptateurs.

Les adaptateurs traduisent des protocoles ou des déclencheurs externes vers des contrats applicatifs neutres. Ils ne contiennent aucune décision métier.

Test architectural : si un service applicatif reçoit ou retourne un objet Request, Response, un modèle Eloquent, un code de statut HTTP, ou tout autre type propre à un framework ou un protocole particulier, une frontière a été violée.

13. Indépendance vis-à-vis de Laravel et de la persistance

Le modèle métier ne dépend jamais de Laravel, d'Eloquent, de HTTP, d'un système de queue particulier, ou d'un fournisseur de stockage. La persistance est un détail externe au métier.

Cette ADR n'impose pas un Repository générique ou systématique pour chaque table. Le principe retenu est :

Une abstraction de persistance est introduite lorsqu'elle protège réellement un agrégat ou une règle métier contre le framework et le stockage.

Les projections et les lectures simples peuvent utiliser des mécanismes dédiés plus directs, tant qu'ils ne contaminent pas le modèle métier et respectent l'ownership des contextes défini au principe 1.


Invariants

  1. Chaque donnée possède un seul Bounded Context propriétaire.
  2. Seul le contexte propriétaire peut modifier cette donnée.
  3. Aucun contexte ne dépend du code interne ou de l'ORM d'un autre.
  4. Un contexte décide de son métier, pas de ses conséquences externes.
  5. Un processus multi-contextes garanti possède une orchestration explicite.
  6. Une transaction ne traverse jamais une frontière de contexte.
  7. Aucun événement n'est envoyé avant le commit du fait qu'il décrit.
  8. Tout consommateur d'événement est idempotent.
  9. Un événement publié est immuable et compatible, ou explicitement versionné.
  10. L'API et les autres interfaces sont des adaptateurs, jamais DMV Core lui-même.
  11. Le métier ne dépend jamais de Laravel ou d'Eloquent.
  12. Cette ADR n'adopte pas implicitement l'Event Sourcing.

Conséquences

  • DMV Core reste un déploiement unique tout en se comportant, en interne, comme un ensemble de composants aussi découplés que des services séparés — une extraction future reste possible sans réécriture, sans être l'objectif recherché aujourd'hui.
  • Plusieurs équipes peuvent travailler simultanément sur des Bounded Contexts différents en ne partageant que des contrats, jamais du code interne.
  • Ajouter un nouveau Bounded Context dans plusieurs années ne nécessite de modifier aucun contexte existant, à condition que ceux-ci publient déjà leurs faits sous forme d'événements.
  • La discipline transactionnelle et l'immutabilité des événements imposent une tolérance explicite à la cohérence éventuelle dans toutes les interfaces consommant DMV Core.
  • L'indépendance vis-à-vis de Laravel et d'Eloquent devient un critère de revue de code, pas seulement une intention.

Points volontairement laissés ouverts

  • La liste précise et le périmètre exact des Bounded Contexts de DMV Core (le mapping avec les modules déjà présents dans api/app/Modules est informatif, il n'est pas figé par cette ADR).
  • Le mécanisme technique de publication des événements (répartiteur en mémoire, file de messages, ou autre) — un détail d'implémentation.
  • La durée de conservation des événements — dépend de contraintes opérationnelles, légales et de confidentialité à documenter séparément.
  • Le moment précis, contexte par contexte, où une abstraction de persistance est justifiée — une décision au cas par cas, pas une règle systématique.
  • L'arborescence de dossiers et toute autre décision d'implémentation.

Alternatives envisagées

AlternativeRaison de non-priorisation
Microservices dès l'origineComplexité opérationnelle disproportionnée à ce stade ; l'extractibilité recherchée par cette ADR rend cette option accessible plus tard, sans en payer le coût maintenant.
Organisation par couches techniques (controllers / models / services) plutôt que par Bounded ContextNe survit à aucune évolution de technologie ou de framework ; ne prévient pas la formation de domaines « Dieu ».
Hiérarchie rigide de dépendance entre domaines « cœur » et domaines « support »Représenterait mal certains besoins métier réels et deviendrait elle-même une source de friction — voir principe 4.
Event Sourcing comme source de vérité par défautDécision structurante distincte, non nécessaire à cette ADR, qui choisit explicitement de ne pas la trancher ici (principe 10).
Repository systématique pour chaque entitéAjoute une indirection sans bénéfice partout où aucun agrégat ni règle métier n'a besoin d'être protégé — voir principe 13.

Risques

  • Le risque le plus concret reste l'écriture croisée accidentelle entre contextes, d'autant plus facile qu'Eloquent ne l'empêche techniquement en rien — cette règle exige une discipline de revue de code, pas seulement une déclaration d'intention.
  • Un processus multi-contextes non identifié comme tel risque d'être absorbé silencieusement par le premier contexte de la chaîne, reconstituant le couplage que cette ADR cherche à éviter (principe 3).
  • Une conservation d'événements insuffisante, faute d'avoir tranché la question laissée ouverte au principe 10, pourrait un jour empêcher le backfill d'un nouveau Bounded Context faute d'historique disponible — à surveiller au moment où ce besoin se présentera.

Liens associés

  • docs/decisions/ADR-000-dmv-core-modular-applications.md
  • docs/decisions/ADR-011-wall-engine-context-engine.md
  • docs/decisions/ADR-012-architecture-foundation-frozen.md
  • docs/06-architecture/26-platform-architecture.md
  • docs/06-architecture/27-architecture-governance.md