Aller au contenu principal

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étierSignification
DossierUn projet pour un client
InterventionUne tâche réalisée sur un dossier
TimesheetLe relevé journalier des heures d'un employé
CompteurUn solde (congés, maladie, heures supplémentaires)
ContratDé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 ServiceApplication Service
Logique métier pureOrchestration
Aucune dépendance techniqueUtilise repositories, bus de messages
Dans la couche domaineDans 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​

AspectDomain EventIntegration Event
PortéeMême contexteEntre contextes
TransportEn mémoireBus de messages
TransactionMême transactionTransactions séparées
CohérenceImmé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.

ConceptNiveauQuestion
L1 CapabilityStratégiqueQuels grands domaines métier ?
L2 CapabilityTactiqueQuelles capacités dans ce domaine ?
L3 CapabilityOpérationnelQuelles fonctions précises ?
Bounded ContextConceptionOù le modèle est-il cohérent ?
AggregateImplémentationQuelle 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