Aller au contenu

Bounded contexts

Découper le domaine en zones de cohérence — la decomposition fonctionnelle qui précédé tout choix de topologie.


Le concept

Quelle que soit la topologie choisie — monolithe modulaire ou microservices — la decomposition fonctionnelle suit les mêmes règles. Le concept clé est le bounded context (contexte delimite), issu du Domain-Driven Design strategique.

Un bounded context est une frontiere explicite à l'intérieur de laquelle un modèle métier est cohérent et unifie. Le même mot peut avoir un sens différent dans deux contextes différents : un Produit dans le contexte Catalog (fiche technique, photos) n'est pas le même que dans le contexte Orders (prix, disponibilité) ni dans Shipping (dimensions, poids).

Cette ambiguite semantique n'est pas un bug — c'est un signal. Quand un même terme désigne des concepts différents selon le département ou l'équipe, on est en présence de bounded contexts distincts. Forcer un modèle unique pour satisfaire tous les usages crée un "God Object" qui porte trop de responsabilités et que tout le monde modifie.

graph LR
    subgraph Catalog["Catalog Context"]
        CAT_P["Product\n(fiche, photos, attributs)"]
    end
    subgraph Orders["Orders Context"]
        ORD_P["Product\n(prix, SKU, dispo)"]
        ORD_O["Order"]
        ORD_O --> ORD_P
    end
    subgraph Shipping["Shipping Context"]
        SHP_P["Product\n(dimensions, poids)"]
        SHP_S["Shipment"]
        SHP_S --> SHP_P
    end
    Catalog -->|"Shared Kernel\n(ProductId)"| Orders
    Orders -->|"Customer-Supplier\n(OrderConfirmed)"| Shipping
    Shipping -.->|"Anti-Corruption Layer"| EXT["Carrier API\n(externe)"]

Chaque contexte possédé son propre Product, avec uniquement les attributs pertinents pour ses opérations. Les contextes communiquent via des identifiants partages (Shared Kernel) ou des événements (Intégration Events), jamais par un accès direct aux données internes d'un autre contexte.


Identifier les bounded contexts

L'identification des bounded contexts est un exercice collaboratif entre développeurs et experts métier. Quatre heuristiques guident le travail.

1. Ambiguite semantique

Chercher les mots qui changent de sens selon le contexte. Si le mot "Client" désigne un prospect pour l'équipe marketing, un acheteur pour l'équipe ventes, et un identifiant technique pour l'équipe support, il y a trois bounded contexts.

2. Frontieres organisationnelles

Conway's Law fait que les frontieres organisationnelles et techniques convergent. Les équipes qui travaillent independamment sur des sujets distincts sont souvent des bounded contexts naturels. Observer qui parle a qui et à quelle fréquence révélé la structure réelle du domaine.

3. Anti-patterns dans les données

Chercher les tables qui sont jointurees de partout. Un modèle de données "central" est un anti-pattern de bounded context. Quand une table products a 80 colonnes dont 30 ne sont utilisées que par un seul module, cette table porte les responsabilités de plusieurs contextes.

4. Event Storming

L'Event Storming (Alberto Brandolini) est un atelier collaboratif ou les participants identifient les événements métier (ce qui se passe), puis les regroupent en clusters. Ces clusters révèlent les bounded contexts.

graph LR
    subgraph "Cluster 1 : Catalog"
        E1["ProductCreated"]
        E2["ProductUpdated"]
        E3["CategoryChanged"]
    end
    subgraph "Cluster 2 : Orders"
        E4["CartCreated"]
        E5["OrderPlaced"]
        E6["OrderConfirmed"]
    end
    subgraph "Cluster 3 : Payments"
        E7["PaymentRequested"]
        E8["PaymentProcessed"]
        E9["RefundIssued"]
    end
    E6 -->|"trigger"| E7

L'Event Storming produit une carte des événements métier en quelques heures. C'est l'outil le plus efficace pour aligner développeurs et experts métier sur la structure du domaine.


Context mapping — les patterns de relation

Une fois les bounded contexts identifiés, il faut définir comment ils interagissent. Le context mapping de DDD propose sept patterns de relation, chacun avec des implications différentes sur le couplage et l'autonomie.

Shared Kernel

Deux contextes partagent un petit sous-modèle commun. Les équipes se coordonnent pour le faire évoluer.

Exemple : ProductId, Money, UserId — des types de base partages
           entre Catalog, Orders et Payments

Le Shared Kernel doit rester petit. S'il grossit, les contextes deviennent couples et la coordination entre équipes augmente. La règle : le Shared Kernel ne contient que des value objects immuables et des identifiants.

Customer-Supplier

Un contexte (le supplier) fournit des données ou services a un autre (le customer). Le supplier adapté son API selon les besoins du customer — la relation est collaborative.

Exemple : Catalog (supplier) fournit les informations produit
           a Orders (customer). Catalog evolue son API en tenant
           compte des besoins d'Orders.

Conformist

Le contexte aval adopte le modèle de l'amont sans negociation. L'amont ne s'adapté pas. On subit son modèle tel quel.

Exemple : Integrer un ERP existant dont on ne peut pas modifier l'API.
           Le contexte interne se conforme au modele de l'ERP.

Le pattern Conformist est acceptable quand le coût d'un Anti-Corruption Layer dépassé le benefice. C'est un choix pragmatique, pas un défaut.

Anti-Corruption Layer (ACL)

Une couche de traduction isole le contexte interne du modèle externe. Le contexte interne garde son propre modèle, l'ACL traduit entre les deux.

graph LR
    subgraph Interne["Contexte interne"]
        M["Modele\ninterne"]
    end
    subgraph ACL["Anti-Corruption Layer"]
        T["Translator"]
        A["Adapter"]
    end
    subgraph Externe["Systeme externe"]
        EXT["Modele\nexterne"]
    end
    M --> T
    T --> A
    A --> EXT

L'ACL est le pattern le plus utilisé pour intégrer des systèmes legacy ou des APIs tierces. Il protégé le modèle interne de la contamination par des concepts externes. Le coût est une couche de code supplémentaire a maintenir, mais le benefice est l'independance du modèle interne.

Open Host Service

Un contexte expose une API publique bien documentee, stable, consommable par plusieurs contextes. C'est l'inverse du Customer-Supplier : l'API est générique, pas adaptée a un consommateur spécifique.

Exemple : Un service de paiement interne expose une API REST
           versionee que tous les contextes utilisent.

Published Language

Un langage commun explicitement documente pour l'intégration. Des schémas d'événements (Avro, Protobuf), des formats de fichiers standardises, des protocoles documentes.

Exemple : Les evenements sur le bus Kafka utilisent des schemas Avro
           versionnes. Un schema registry garantit la compatibilite.

Separate Ways

Deux contextes n'ont pas besoin de s'intégrer. Chacun géré sa propre version du concept, sans communication. C'est souvent le bon choix quand l'intégration couterait plus qu'elle ne rapporte.

Exemple : Le contexte Reporting et le contexte Auth n'ont pas
           de modele commun — chacun son chemin.

Context map : vue d'ensemble

La context map est un diagramme qui représenté tous les bounded contexts et leurs relations. C'est l'artefact le plus important du DDD strategique.

graph TB
    subgraph Core["Core Domain"]
        CATALOG["Catalog\nContext"]
        ORDERS["Orders\nContext"]
        PRICING["Pricing\nContext"]
    end
    subgraph Supporting["Supporting Domain"]
        SHIPPING["Shipping\nContext"]
        NOTIF["Notification\nContext"]
        LOYALTY["Loyalty\nContext"]
    end
    subgraph Generic["Generic Domain"]
        AUTH["Auth\nContext"]
        BILLING["Billing\nContext"]
    end
    subgraph External["Externe"]
        CARRIER["Carrier\nAPI"]
        PSP["Payment\nService Provider"]
    end
    CATALOG -->|"Shared Kernel\n(ProductId)"| ORDERS
    CATALOG -->|"Customer-Supplier"| PRICING
    ORDERS -->|"Customer-Supplier"| SHIPPING
    ORDERS -->|"Published Language\n(events)"| NOTIF
    ORDERS -->|"Published Language\n(events)"| LOYALTY
    PRICING -->|"Open Host\nService"| ORDERS
    SHIPPING -.->|"ACL"| CARRIER
    BILLING -.->|"ACL"| PSP
    AUTH ---|"Separate Ways"| NOTIF

Cette carte se lit comme un plan de metro. Les lignes entre contextes indiquent le type de relation. Les contextes sans connexion (Separate Ways) sont volontairement isoles.


Exemple pratique : e-commerce

Un e-commerce typique decompose en bounded contexts :

Bounded Context Type Responsabilité Entités principales
Catalog Core Gestion des fiches produit, catégories, recherche Product, Category, Attribute
Orders Core Cycle de vie des commandes Order, OrderLine, Cart
Pricing Core Règles de prix, promotions, remises PriceRule, Discount, Coupon
Payments Generic Traitement des paiements (délégué a un PSP) Payment, Refund, Transaction
Shipping Supporting Expedition, suivi des colis Shipment, TrackingEvent, Address
Inventory Supporting Gestion des stocks, reservation Stock, Reservation, Warehouse
Notifications Supporting Emails, SMS, push notifications Template, Channel, Recipient
Identity Generic Authentification, autorisations User, Rôle, Permission
Loyalty Supporting Points de fidelite, avantages LoyaltyAccount, Points, Reward

Observations :

  • Le Product existe dans Catalog (fiche), Orders (référence), Pricing (prix), Inventory (stock), Shipping (dimensions). Ce n'est pas de la duplication — ce sont des projections différentes du même concept physique, adaptées aux besoins de chaque contexte.
  • Les contextes Generic (Payments, Identity) sont des candidats à l'achat ou à l'intégration de SaaS (Stripe, Auth0). Investir du temps de développement sur un contexte Generic est rarement un bon usage des ressources.
  • Les contextes Core (Catalog, Orders, Pricing) sont ceux ou l'avantage concurrentiel se joue. C'est la qu'on investit les meilleurs développeurs et le plus de temps de conception.

Bounded contexts et topologie

Les bounded contexts sont orthogonaux à la topologie. Un bounded context peut être :

  • Un module dans un monolithe modulaire
  • Un microservice
  • Une fonction serverless
  • Un ensemble de fonctions serverless

La frontiere est la même — seul le mécanisme de communication change (appel de fonction vs appel réseau vs événement).

La recommandation : dessiner les bounded contexts d'abord, choisir la topologie ensuite. Les contextes révèlent la structure naturelle du domaine. La topologie est une décision d'implémentation qui dépend du contexte organisationnel (taille d'équipe, maturité ops) et technique (volume, scalabilité).

Bounded context = unite de déploiement potentielle

Un bounded context bien delimite est un candidat naturel à l'extraction en service indépendant. Mais l'extraction n'est justifiee que quand un signal concret le demande (scalabilité differentielle, équipe autonome, technologie différente). Sans signal, le module dans le monolithe est suffisant.


Chapitre suivant : Integration de systèmes — topologies d'intégration, patterns EIP et ingénierie des systèmes.