Skip to content

Domain Driven Design : Guide Pratique

Introduction

Ce document résume les concepts fondamentaux du Domain Driven Design (DDD) appliqués à un contexte .NET, illustrés par un exemple concret de gestion de dossiers, timesheets et RH.


Langage Ubiquitaire (Ubiquitous Language)

Le code doit parler le même langage que le métier. Chaque terme a une définition précise, partagée entre développeurs et experts métier.

Terme métier Signification
Dossier Un projet pour un client
Intervention Une tâche réalisée sur un dossier
Timesheet Le relevé journalier des heures d'un employé
Compteur Un solde (congés, maladie, heures supplémentaires)
Contrat Définit les jours et durées de travail d'un employé
Demande de Congé Une réservation d'absence sur un compteur

Règle d'or : si les utilisateurs parlent de "dossier", le code contient une classe Dossier, pas Project ou Folder.


Bounded Context

Un Bounded Context est une frontière où les termes ont une signification précise et où le modèle est cohérent.

Le même mot peut avoir des significations différentes selon le contexte. Par exemple, "Employé" dans le contexte RH (contrat, compteurs, absences) n'a pas les mêmes attributs que "Employé" dans le contexte Gestion des Dossiers (juste un identifiant et un nom).

Exemple de découpage

CONTEXTE : Gestion des Dossiers
- Dossier
- Intervention
- Client
- Employé (référence simplifiée)

CONTEXTE : Timesheet
- TimesheetJournalière
- LigneDeTemps

CONTEXTE : RH
- Employé (détaillé)
- Contrat
- Compteur
- DemandeDeCongé

Communication entre contextes

Les contextes communiquent via des événements (Integration Events), jamais par accès direct aux données d'un autre contexte.


Aggregates

Un Aggregate est un groupe d'objets formant une unité cohérente avec une porte d'entrée unique appelée Aggregate Root.

Principes

  • Toute modification passe par l'Aggregate Root
  • L'Aggregate Root protège les règles métier (invariants)
  • Un Aggregate = une transaction = un repository
  • Les entités enfants ne sont jamais accessibles directement de l'extérieur

Question clé pour définir les frontières

"Ces données doivent-elles être cohérentes immédiatement, dans la même transaction ?"

  • Si oui → même Aggregate
  • Si non (ça peut attendre quelques secondes) → Aggregates séparés

Exemple

AGGREGATE Dossier
├── Dossier (Root)
│   - Connaît son statut (ouvert/clôturé)
│   - Contrôle l'ajout d'interventions
│   - Fait respecter les règles métier
└── Intervention (entités enfants)

Règle protégée : "On ne peut pas ajouter d'intervention sur un dossier clôturé"

Référencer un autre Aggregate

Un Aggregate référence un autre Aggregate uniquement par son identifiant, jamais par référence directe.

TimesheetJournalière
└── LigneDeTemps
    - dossierId (référence par ID, pas l'objet Dossier)
    - durée

Entity vs Value Object

Entity

Un objet défini par son identité. Deux entities avec les mêmes valeurs mais des identités différentes sont différentes.

Exemples : Employé, Dossier, Intervention, DemandeDeCongé, Timesheet, Contrat, Compteur

Value Object

Un objet défini uniquement par ses valeurs. Deux Value Objects avec les mêmes valeurs sont identiques et interchangeables.

Exemples : Durée, Période (date + tranche horaire), LieuDeTravail, Montant

Avantages des Value Objects

  • Encapsulent des règles de validation (une Durée ne peut pas être négative)
  • Encapsulent des comportements (une Période sait si elle chevauche une autre)
  • Rendent le code plus expressif et typé

Domain Services

Un Domain Service contient de la logique métier qui n'appartient naturellement à aucun Aggregate.

Caractéristiques

  • Sans état (pas de données internes)
  • Logique métier pure
  • Opère sur des valeurs, pas directement sur des Aggregates
  • Son nom fait partie du langage ubiquitaire

Exemple : CalculateurHeuresSupplementaires

Entrées :
- tempsPresté (Durée)
- tempsContractuel (Durée)

Sortie :
- différence (Durée) : positive = heures sup, négative = déficit

Règle métier :
différence = tempsPresté - tempsContractuel

Domain Service vs Application Service

Domain Service Application Service
Logique métier pure Orchestration
Aucune dépendance technique Utilise repositories, bus de messages
Dans la couche domaine Dans la couche application

Événements

Domain Event

Représente un fait métier qui s'est produit. Toujours nommé au passé.

Caractéristiques : - Immutable - Contient les informations nécessaires pour comprendre ce qui s'est passé - Émis par l'Aggregate après une action réussie - Traité au sein du même Bounded Context - Généralement dans la même transaction

Exemples : - InterventionAjoutée - TimesheetJournalièreSoumise - DemandeDeCongéCréée

Integration Event

Notifie les autres contextes de ce qui s'est passé.

Caractéristiques : - Publié vers l'extérieur du Bounded Context - Transporté via un bus de messages (RabbitMQ, Azure Service Bus, etc.) - Traité de manière asynchrone - Cohérence à terme (eventual consistency)

Différences clés

Aspect Domain Event Integration Event
Portée Même contexte Entre contextes
Transport En mémoire Bus de messages
Transaction Même transaction Transactions séparées
Cohérence Immédiate À terme

Contenu d'un événement

Un événement ne contient que ce qui est pertinent, pas tout l'Aggregate.

TimesheetJournalièreValidée
├── timesheetId
├── employéId
├── date
├── tempsTotalPresté
└── occurredAt

(Pas besoin de : lieuDeTravail, pauseMidi, détail des lignes, etc.)

Quand Domain et Integration Event sont identiques

Si le contenu est le même, la différence réside dans le transport et le moment du traitement. Une même classe peut servir aux deux usages.


Flux complet : exemple

Scénario : Validation d'une timesheet et mise à jour des heures sup

1. Manager valide la timesheet
   └─► TimesheetJournalière.valider()
       └─► Domain Event : TimesheetJournalièreValidée

2. Event publié comme Integration Event
   └─► Bus de messages

3. Contexte RH reçoit l'événement
   └─► Application Service
       ├─► Récupère l'Employé (avec son Contrat)
       ├─► Appelle CalculateurHeuresSupplementaires (Domain Service)
       └─► Met à jour le Compteur
           └─► Domain Event : CompteurMisÀJour

Capabilities vs Bounded Contexts

Business Capability Map (vue stratégique)

Répond à "Que fait l'entreprise ?"

  • L0 : L'entreprise
  • L1 : Grands domaines métier (RH, Opérations)
  • L2 : Capacités (Suivi des Dossiers, Gestion des Absences)
  • L3 : Sous-capacités (Création dossier, Demande congé)

Bounded Context (vue conception)

Répond à "Comment modélise-t-on le logiciel ?"

Relation

Souvent un L2 correspond à un Bounded Context, mais pas toujours. Plusieurs L2 peuvent être regroupés dans un même contexte s'ils partagent le même langage et les mêmes règles.

Concept Niveau Question
L1 Capability Stratégique Quels grands domaines métier ?
L2 Capability Tactique Quelles capacités dans ce domaine ?
L3 Capability Opérationnel Quelles fonctions précises ?
Bounded Context Conception Où le modèle est-il cohérent ?
Aggregate Implémentation Quelle unité de cohérence ?

Modèle complet de l'exemple

CONTEXTE : Gestion des Dossiers
─────────────────────────────────────────
AGGREGATE Dossier
├── Dossier (Root)
└── Intervention

AGGREGATE Client
└── Client (Root)

Événements : InterventionAjoutée, InterventionModifiée,
             DossierOuvert, DossierClôturé


CONTEXTE : Timesheet
─────────────────────────────────────────
AGGREGATE TimesheetJournalière
├── TimesheetJournalière (Root)
│   - employéId
│   - date
│   - statut (brouillon / soumise / validée / rejetée)
│   - lieuDeTravail (Value Object)
│   - pauseMidi (Value Object)
└── LigneDeTemps
    - dossierId (référence)
    - durée (Value Object)
    - description

Règles métier :
- Soumettre uniquement si lieuDeTravail et pauseMidi renseignés
- Non modifiable après validation

Événements : TimesheetJournalièreSoumise, TimesheetJournalièreValidée,
             TimesheetJournalièreRejetée


CONTEXTE : RH
─────────────────────────────────────────
AGGREGATE Employé
├── Employé (Root)
├── Contrat
│   - jours de prestation avec durées
└── Compteur (plusieurs : congés, maladie, heures sup)

AGGREGATE DemandeDeCongé
└── DemandeDeCongé (Root)
    - employéId
    - compteurType
    - période (Value Object)
    - statut (en attente / validée / refusée)

Règles métier :
- Pas deux demandes sur la même tranche horaire
- Compteur décrémenté à la création, recédité si refus
- Heures sup calculées jour par jour

Événements : DemandeDeCongéCréée, DemandeDeCongéValidée,
             DemandeDeCongéRefusée, CompteurMisÀJour

Domain Service : CalculateurHeuresSupplementaires

Pour aller plus loin

  • Value Objects : implémentation et patterns
  • Context Mapping : patterns de relation entre contextes (ACL, Shared Kernel, Customer/Supplier)
  • CQRS : séparation lecture/écriture
  • Event Sourcing : stocker les événements plutôt que l'état
  • Structure projet .NET : organisation des couches et dossiers