D'un brief métier à un domaine modélisé
Bootcode IWA-S04 — Semaine 17, Jour 5
1. Extraire les concepts clés
À partir d'un brief métier
2. Identifier les bounded contexts
Découper le projet en contextes
3. Modéliser agrégats, value objects, événements
Les briques du domaine
4. Lister les use cases
À partir des besoins métier
Le brief métier FitClub
Une salle de sport — les étudiants suivent en temps réel
Étape par étape : les bounded contexts
Membership, Coaching, Billing…
Agrégats, value objects, événements, use cases
On modélise ensemble, étape par étape
La méthode : noms → concepts, verbes → actions
Souligner les noms et les verbes dans le brief
L'exercice de l'après-midi
Chacun choisit un domaine et modélise
Module 1
Une salle de sport qui veut se digitaliser
📋 Le projet
FitClub est une salle de sport qui veut lancer une plateforme digitale. Les membres peuvent s'inscrire avec leur email, choisir un type d'abonnement (mensuel ou annuel) et payer leur cotisation.
Chaque membre peut réserver une session avec un coach. Le coach est assigné à la session selon sa spécialité. La session a une durée et un créneau horaire.
Quand un membre s'inscrit, un événement MemberSubscribed est émis. Quand une session est réservée, SessionBooked est émis. Le paiement déclenche PaymentProcessed.
L'administration peut suspendre un membre, réactiver un abonnement et consulter les statistiques de fréquentation.
📝 Suivez en temps réel
Soulignez les noms (concepts) en bleu et les verbes (actions) en rouge
Deux couleurs, deux catégories
On lit le brief et on surligne ce qui compte
🔵
Les NOMS → concepts
Membre, abonnement, session, coach, paiement…
→ Deviennent des agrégats ou value objects
🔴
Les VERBES → actions
S'inscrire, réserver, payer, suspendre…
→ Deviennent des use cases ou des événements
🎯 La méthode est plus importante que le résultat
Le vocabulaire métier doit se retrouver dans le code
Module 2
Découper le projet en contextes métier
L'analogie des départements
Une entreprise a des départements : RH, Compta, Ventes… Chacun a son vocabulaire et ses règles. Un bounded context = un département du logiciel.
Membership
Inscription, profil, email, type d'abonnement
Coaching
Sessions, coachs, spécialités, créneaux, réservations
Billing
Paiements, cotisations, factures
Reporting
Statistiques de fréquentation, administration
Chaque contexte a son propre vocabulaire et ses propres règles
🆔
Membership
Membre, email, abonnement, suspension
« Un membre a un email unique »
🏋️
Coaching
Session, coach, spécialité, créneau
« Une session a un coach et une durée »
💳
Billing
Paiement, cotisation, facture
« Un paiement déclenche PaymentProcessed »
📊
Reporting
Statistiques, fréquentation, admin
« L'admin consulte les stats »
💡 Chaque contexte est indépendant — ils communiquent par événements
Module 3
Les racines de cohérence du domaine
Un agrégat = une racine de cohérence
On regroupe les concepts qui doivent rester cohérents ensemble. La racine est l'entité qu'on manipule directement.
🆔
Member (Membership)
Racine : Member — contient email, statut, abonnement
📋
Subscription (Membership)
Racine : Subscription — type, dates, statut
🏋️
Session (Coaching)
Racine : Session — membre, coach, créneau, durée
🧑🏫
Coach (Coaching)
Racine : Coach — nom, spécialités, disponibilité
💡 On manipule toujours l'agrégat via sa racine
Pas d'accès direct aux objets internes — la cohérence est garantie
// membership/aggregate/Member.ts
export class Member {
private constructor(
public readonly id: string,
private email: Email,
private status: MemberStatus,
private subscription: Subscription | null,
) {}
static subscribe(email: Email, type: MembershipType): Member {
return new Member(crypto.randomUUID(), email, "active", new Subscription(type));
}
suspend(): void {
if (this.status === "suspended") throw new Error("Déjà suspendu");
this.status = "suspended";
}
}
💡 La racine Member protège sa cohérence interne
On ne peut pas suspendre un membre déjà suspendu — la règle est dans l'agrégat
Module 4
Des concepts sans identité, définis par leur valeur
Un value object = défini par sa valeur, pas par une identité
Deux emails identiques sont interchangeables. Pas besoin d'ID — la valeur suffit.
📧
Valide le format, immuable
🎫
MembershipType
Mensuel ou annuel
⏱️
SessionDuration
30, 45 ou 60 minutes
🕐
TimeSlot
Créneau horaire (début + fin)
🏷️
Specialty
Musculation, cardio, yoga…
💰
Money
Montant + devise
💡 Les value objects valident à la construction — pas d'état invalide possible
// membership/value-objects/MembershipType.ts
export class MembershipType {
private static readonly VALUES = ["monthly", "annual"] as const;
constructor(private readonly value: string) {
if (!MembershipType.VALUES.includes(value as any)) {
throw new Error("Type invalide");
}
}
isAnnual(): boolean {
return this.value === "annual";
}
price(): number {
return this.isAnnual() ? 480 : 45;
}
equals(other: MembershipType): boolean {
return this.value === other.value;
}
}
💡 Pas d'ID — deux MembershipType("annual") sont égaux
La validation est dans le constructeur : impossible de créer un type invalide
Module 5
Les faits passés du domaine
Un événement = un fait qui s'est produit dans le passé
Toujours au passé : « MemberSubscribed », jamais « SubscribeMember ». C'est ce qui s'est passé, pas ce qu'on veut faire.
MemberSubscribed
Émis quand un membre s'inscrit avec un type d'abonnement
SessionBooked
Émis quand une session est réservée avec un coach
CoachAssigned
Émis quand un coach est assigné à une session
PaymentProcessed
Émis quand un paiement de cotisation est traité
MemberSuspended
Émis quand l'admin suspend un membre
// membership/events/MemberSubscribed.ts
export class MemberSubscribed {
constructor(
public readonly memberId: string,
public readonly email: string,
public readonly membershipType: string,
public readonly occurredAt: Date = new Date(),
) {}
static from(member: Member): MemberSubscribed {
return new MemberSubscribed(
member.id,
member.email.toString(),
member.subscription?.type.value ?? "none",
);
}
}
💡 L'événement capture l'état au moment du fait
Immuable — on ne modifie jamais un événement, on le consomme
Module 6
Les actions que le système exécute
Un use case = une action (verbe) que le système exécute
Contrairement à l'événement (fait passé), le use case est l'intention : « SubscribeMember », pas « MemberSubscribed ».
📝
SubscribeMember
Inscrit un membre avec un type d'abonnement → émet MemberSubscribed
📅
BookSession
Réserve une session pour un membre → émet SessionBooked
🧑🏫
AssignCoach
Assigne un coach à une session → émet CoachAssigned
💳
ProcessPayment
Traite un paiement de cotisation → émet PaymentProcessed
🚫
SuspendMember
Suspend un membre (admin) → émet MemberSuspended
🔄
ReactivateSubscription
Réactive un abonnement suspendu
// membership/usecases/SubscribeMember.ts
export class SubscribeMember {
constructor(
private memberRepo: MemberRepository,
private eventBus: EventBus,
) {}
async execute(cmd: { email: string; type: string }): Promise<string> {
const email = new Email(cmd.email);
const type = new MembershipType(cmd.type);
const member = Member.subscribe(email, type);
await this.memberRepo.save(member);
await this.eventBus.publish(MemberSubscribed.from(member));
return member.id;
}
}
💡 Le use case orchestre : crée l'agrégat, sauvegarde, publie l'événement
Une classe, une action, execute() — la logique est ici
❌ Un seul contexte pour tout
Vouloir tout mettre dans Membership — sessions, paiements, stats…
→ Un monolithe conceptuel, impossible à découper
✅ Séparer par vocabulaire
Membership parle de membres, Coaching de sessions, Billing de paiements
→ Analogie des départements : chacun son métier
❌ Confondre use case et événement
Nommer un événement « SubscribeMember » (présent/intention)
→ On ne sait plus si c'est une action ou un fait
✅ Use case = verbe, événement = passé
SubscribeMember (action) → MemberSubscribed (fait passé)
→ Le use case déclenche, l'événement constate
❌ Mauvais nommage métier
Appeler un membre « User » ou un abonnement « Plan »
→ Le vocabulaire technique remplace le vocabulaire métier
✅ Le vocabulaire métier dans le code
Member, Subscription, Session, Coach — les mots du brief
→ Le code parle le même langage que le métier
🎯 Chacun choisit un domaine et modélise
Appliquez la méthode vue ce matin sur votre propre brief
Choisissez un domaine
Bibliothèque, livraison de repas, gestion de stock, école…
Écrivez un brief de 5-6 lignes
Décrivez le métier comme si vous parliez au client
Soulignez les noms (bleu) et les verbes (rouge)
La méthode d'analyse — c'est la partie la plus importante
Modélisez : contexts, agrégats, value objects, événements, use cases
Un schéma sur papier ou au tableau — pas de code encore
⏱️ Présentation à 16h — chacun présente sa modélisation
On va implémenter un agrégat event-sourcé complet
Tout ce qu'on a modélisé aujourd'hui servira de base
📦
Les agrégats
Member, Session… deviennent event-sourcés
⚡
Les événements
MemberSubscribed… sont stockés et rejoués
🔄
Reconstruction d'état
On rejoue les événements pour reconstruire l'agrégat
🎯
Use cases event-sourcés
SubscribeMember émet et persiste l'événement
💡 La modélisation d'aujourd'hui est le socle de l'implémentation de demain
🔍
Noms → concepts, verbes → actions
La méthode d'analyse en deux couleurs
🗂️
Bounded contexts = départements
Membership, Coaching, Billing, Reporting
📦
Agrégats + value objects + événements
Les briques du domaine modélisé
🗣️
Le vocabulaire métier dans le code
Member, pas User — Subscription, pas Plan
Choisissez votre domaine et modélisez cet après-midi
Semaine 18 : implémenter un agrégat event-sourcé complet