Domain Events

Décrire les faits passés du domaine

Bootcode IWA-S04 — Semaine 17, Jour 3

Objectifs de la leçon

L'étudiant doit savoir :

1. Créer des événements

Avec les bonnes données readonly

2. Produire des événements

Dans un agrégat

3. Comprendre le découplage

L'agrégat publie, les handlers réagissent

Plan du cours

1

Le concept

Un événement = un fait passé (UserSignedUp, OrderPlaced)

2

La rĂšgle de nommage

Toujours au passé, factuel, lié au domaine

3

Commande vs ÉvĂ©nement

Intention (futur) vs fait accompli (passé)

4

Pourquoi les événements ?

Historique, audit, découplage

5

Live coding

Un agrégat Article qui produit des événements

Module 1

Qu'est-ce qu'un événement ?

Un fait passé dans le domaine

Un événement = un fait passé

Quelque chose S'EST PASSÉ dans le domaine

Un Ă©vĂ©nement dĂ©crit un fait qui a dĂ©jĂ  eu lieu — il est immuable

đŸ‘€

UserSignedUp

Un utilisateur s'est inscrit

📩

OrderPlaced

Une commande a été passée

📝

ArticlePublished

Un article a été publié

💾

PaymentReceived

Un paiement a été reçu

💡 La clĂ© : le passĂ©

On ne dit pas ce qu'on VEUT faire, on dit ce qui S'EST PASSÉ

Analogie : le journal d'un domaine

Un événement = une entrée dans le journal

Le domaine tient un journal de tout ce qui s'y passe

📅 2025-01-15 10:32

UserSignedUp { userId: "u-42", email: "alice@mail.com" }

📅 2025-01-15 10:35

ArticleCreated { articleId: "a-7", title: "Hello DDD" }

📅 2025-01-15 11:00

ArticlePublished { articleId: "a-7", publishedAt: "11:00" }

💡 Chaque entrĂ©e est un fait

On ne peut ni modifier ni effacer une entrĂ©e — elle est arrivĂ©e, point

Module 2

La rĂšgle de nommage

Au passé · factuel · lié au domaine

3 rÚgles pour nommer un événement

1ïžâƒŁ Toujours au passĂ©

Le verbe est conjugué au passé : Created, SignedUp, Placed

2ïžâƒŁ Factuel

On dĂ©crit un fait, pas une intention : ArticlePublished ✓ — pas PublishArticle ✗

3ïžâƒŁ LiĂ© au domaine (mĂ©tier)

Le nom parle le langage du mĂ©tier : UserSignedUp ✓ — pas RowInserted ✗

💡 Le test ultime

Lisez le nom Ă  voix haute : "UserSignedUp" — est-ce que votre mĂ©tier comprend ?

Nommage : technique vs métier

❌ Nommage technique

RowInserted

RecordSaved

DbUpdated

EmailSentEvent

Le mĂ©tier ne comprend pas — c'est du jargon de dĂ©veloppeur

✅ Nommage mĂ©tier

UserSignedUp

OrderPlaced

ArticlePublished

WelcomeEmailSent

Le mĂ©tier comprend — c'est son langage

💡 L'Ă©vĂ©nement parle le langage ubiquitaire

Si votre expert métier ne comprend pas le nom, c'est mauvais signe

Module 3

Commande vs ÉvĂ©nement

Intention vs fait accompli

Deux concepts Ă  ne pas confondre

📹 Commande

Une intention — on VEUT faire quelque chose

Tournée vers le futur

CreateArticle

SignUpUser

PlaceOrder

"Je veux créer un article"

📱 ÉvĂ©nement

Un fait accompli — ça S'EST PASSÉ

Tourné vers le passé

ArticleCreated

UserSignedUp

OrderPlaced

"Un article a été créé"

💡 La commande prĂ©cĂšde l'Ă©vĂ©nement

CreateArticle (commande) → ArticleCreated (Ă©vĂ©nement qui en rĂ©sulte)

CreateArticle vs ArticleCreated

📹 Commande (intention)

class CreateArticle {

constructor(

readonly title: string,

readonly content: string,

readonly authorId: string,

) {}

}

"Je veux crĂ©er cet article" — peut Ă©chouer

📱 ÉvĂ©nement (fait)

class ArticleCreated {

constructor(

readonly articleId: string,

readonly title: string,

readonly occurredAt: Date,

) {}

}

"L'article a Ă©tĂ© créé" — c'est fait, immuable

💡 La diffĂ©rence visible

La commande contient ce qu'on VEUT faire — l'Ă©vĂ©nement contient ce qui S'EST PASSÉ + un horodatage

Module 4

Pourquoi les événements ?

Historique · Audit · Découplage

Historique & audit

Tout est tracé

Chaque événement est une trace immuable de ce qui s'est passé

📜

Historique complet

On peut rejouer l'historique pour reconstruire l'état actuel

🔍

Audit

"Qui a fait quoi et quand ?" — la rĂ©ponse est dans les Ă©vĂ©nements

// L'historique d'un article

ArticleCreated { articleId: "a-7", title: "Hello" }

ArticleEdited { articleId: "a-7", newTitle: "Hello DDD" }

ArticlePublished { articleId: "a-7", at: "11:00" }

💡 C'est la base de l'event sourcing (S18)

On reconstruit l'Ă©tat en rejouant les Ă©vĂ©nements — comme un Git log

Le découplage : l'avantage majeur

L'émetteur ne connaßt pas les récepteurs

L'agrĂ©gat publie un Ă©vĂ©nement — il ne sait PAS qui va rĂ©agir

🧠 AgrĂ©gat Article

Produit ArticlePublished

↓ publie l'Ă©vĂ©nement ↓

ArticlePublished { articleId, title, at }

↓ des handlers y rĂ©agissent, indĂ©pendamment ↓

📧 EmailHandler

Notifie les abonnés

📊 StatsHandler

Met Ă  jour les stats

🔍 IndexHandler

Indexe pour la recherche

💡 L'agrĂ©gat ne sait pas qu'ils existent

On peut ajouter/supprimer des handlers sans toucher à l'agrégat

Module 5

Live coding

Un agrégat Article qui produit des événements

1ïžâƒŁ La classe d'Ă©vĂ©nement

Une classe simple avec des donnĂ©es readonly — aucune logique

// domain/events/ArticleCreated.ts

export class ArticleCreated {

constructor(

readonly articleId: string,

readonly title: string,

readonly authorId: string,

readonly occurredAt: Date = new Date(),

) {}

}

💡 3 choses à remarquer

1ïžâƒŁ Tout est readonly — immuable · 2ïžâƒŁ Aucune mĂ©thode, juste des donnĂ©es · 3ïžâƒŁ Un horodatage par dĂ©faut

2ïžâƒŁ L'agrĂ©gat produit des Ă©vĂ©nements

L'agrégat collecte les événements qu'il déclenche

// domain/aggregates/Article.ts

export class Article {

private events: object[] = [];

constructor(

readonly id: string,

readonly title: string,

readonly authorId: string,

) {

// L'agrégat publie un événement

this.events.push(new ArticleCreated(

id, title, authorId

));

}

pullEvents(): object[] {

const copy = [...this.events];

this.events = [];

return copy;

}

}

💡 L'agrĂ©gat ne sait pas qui rĂ©agit

Il se contente de collecter les Ă©vĂ©nements — on les rĂ©cupĂšre avec pullEvents()

3ïžâƒŁ Le handler rĂ©agit Ă  l'Ă©vĂ©nement

Un handler est totalement indépendant de l'agrégat

// infra/handlers/NotifySubscribersOnArticleCreated.ts

export class NotifySubscribersOnArticleCreated {

constructor(private mailer: Mailer) {}

async handle(event: ArticleCreated): Promise<void> {

// Réagit au fait : un article a été créé

const subscribers = await this.getSubscribers();

for (const sub of subscribers) {

await this.mailer.send(sub.email, {

subject: "Nouvel article : " + event.title,

});

}

}

}

💡 Le handler ne connaĂźt que l'Ă©vĂ©nement

Il ne sait pas que l'Ă©vĂ©nement vient d'un Article — dĂ©couplage total

Le flux complet

AgrĂ©gat → ÉvĂ©nement → Handler

1ïžâƒŁ L'agrĂ©gat agit

Un Article est créé — l'agrĂ©gat publie ArticleCreated

↓

2ïžâƒŁ L'Ă©vĂ©nement est collectĂ©

article.pullEvents() retourne les événements accumulés

↓

3ïžâƒŁ Les handlers rĂ©agissent

Chaque handler reçoit l'événement et fait son travail, indépendamment

const article = new Article(id, title, authorId);

const events = article.pullEvents();

// → dispatcher.dispatch(events) → handlers

PiÚges courants à éviter

❌ Confondre commande et Ă©vĂ©nement

✅ Insister sur passĂ© vs futur : CreateArticle (commande) ≠ ArticleCreated (Ă©vĂ©nement)

❌ Nommer les Ă©vĂ©nements de façon technique

✅ Utiliser le langage mĂ©tier : RowInserted ✗ → UserSignedUp ✓

❌ Vouloir modifier ou supprimer un Ă©vĂ©nement

✅ Un Ă©vĂ©nement est un fait — il est immuable. On ne le change pas, on en produit un nouveau

Lien avec la Semaine 18

Les événements sont la base de l'event sourcing

La semaine prochaine, on pousse le concept plus loin

📩

Aujourd'hui (S17)

L'agrégat produit des événements en plus de changer d'état

🔼

Semaine 18 (Event Sourcing)

L'Ă©tat EST reconstruit en rejouant les Ă©vĂ©nements — comme un Git log

💡 Ce que vous apprenez aujourd'hui prĂ©pare S18

Maßtriser les événements maintenant = event sourcing plus facile ensuite

À retenir !

âȘ

ÉvĂ©nement = passĂ©

ArticleCreated, pas CreateArticle

🔌

Découplage total

L'émetteur ne connaßt pas les récepteurs

📩

Classe simple, données readonly

Aucune logique — juste des faits

🔒

Immuable

Un Ă©vĂ©nement est un fait — on ne le modifie pas

Questions ?

Créez un agrégat qui produit un événement dans votre projet

Demain : Bounded Contexts — dĂ©limiter son domaine