Event Sourcing
- architecture
- design-patterns
- scalabilité
Imaginez une banque qui ne stockerait que votre solde actuel. Pas d'historique de transactions. Pas de relevé. Juste un chiffre : "Votre solde est 1 247,30€." Comment savoir si un prélèvement a été effectué deux fois ? Comment prouver qu'un virement a bien été envoyé ? Impossible. C'est absurde — et pourtant, c'est exactement ce que fait la majorité des applications : écraser l'état précédent à chaque modification.
L'Event Sourcing prend l'approche inverse : stocker chaque événement qui a conduit à l'état actuel.
Le principe
Au lieu de stocker l'état final dans une base de données et de le modifier par des UPDATE, l'Event Sourcing stocke une séquence immuable d'événements. L'état actuel est recalculé en rejouant ces événements.
Approche classique (CRUD) :
UPDATE comptes SET solde = 1247.30 WHERE id = 42
Event Sourcing :
CompteOuvert { id: 42, solde_initial: 0 }
DépôtEffectué { id: 42, montant: 2000 }
RetraitEffectué { id: 42, montant: 500 }
VirementReçu { id: 42, montant: 150 }
PrélèvementExécuté { id: 42, montant: 402.70 }
→ État recalculé : solde = 0 + 2000 - 500 + 150 - 402.70 = 1247.30
Le résultat est le même. Mais avec l'Event Sourcing, vous savez comment vous êtes arrivé là.
L'Event Store
Les événements sont stockés dans un Event Store — un journal append-only (on ne modifie jamais, on ne supprime jamais, on ajoute toujours).
interface DomainEvent {
eventId: string;
aggregateId: string;
type: string;
data: Record<string, unknown>;
timestamp: Date;
version: number;
}
// Exemple d'événements pour un compte bancaire
const events: DomainEvent[] = [
{
eventId: "evt-1", aggregateId: "compte-42",
type: "CompteOuvert",
data: { titulaire: "Nicolas", soldeInitial: 0 },
timestamp: new Date("2026-01-15"), version: 1
},
{
eventId: "evt-2", aggregateId: "compte-42",
type: "DépôtEffectué",
data: { montant: 2000, origine: "Virement employeur" },
timestamp: new Date("2026-01-31"), version: 2
},
{
eventId: "evt-3", aggregateId: "compte-42",
type: "RetraitEffectué",
data: { montant: 500, distributeur: "ATM-Paris-12" },
timestamp: new Date("2026-02-03"), version: 3
}
];Chaque événement est immuable — une fois écrit, il ne change jamais. C'est la règle fondamentale.
Reconstruire l'état
Pour obtenir l'état actuel d'un agrégat, on rejoue tous ses événements :
class CompteBancaire {
private solde: number = 0;
private statut: string = "inactif";
private titulaire: string = "";
// Appliquer un événement pour mettre à jour l'état
apply(event: DomainEvent): void {
switch (event.type) {
case "CompteOuvert":
this.titulaire = event.data.titulaire as string;
this.solde = event.data.soldeInitial as number;
this.statut = "actif";
break;
case "DépôtEffectué":
this.solde += event.data.montant as number;
break;
case "RetraitEffectué":
this.solde -= event.data.montant as number;
break;
case "CompteFermé":
this.statut = "fermé";
break;
}
}
// Reconstruire depuis l'historique
static fromEvents(events: DomainEvent[]): CompteBancaire {
const compte = new CompteBancaire();
events.forEach(e => compte.apply(e));
return compte;
}
}
// Reconstruire l'état actuel
const compte = CompteBancaire.fromEvents(events);
// → { solde: 1500, statut: "actif", titulaire: "Nicolas" }Les bénéfices
Audit complet
Chaque action est tracée. Qui a fait quoi, quand, pourquoi. Pour la finance, la santé, le juridique — partout où la traçabilité est une obligation — l'Event Sourcing est naturel.
Replay et debug
Un bug en production ? Rejouez les événements pour reproduire exactement l'état qui a causé le problème. Pas besoin de deviner — vous avez l'historique complet.
// Reproduire l'état au moment du bug
const eventsUntilBug = events.filter(e => e.timestamp <= bugTimestamp);
const stateAtBug = CompteBancaire.fromEvents(eventsUntilBug);Voyage dans le temps
Vous pouvez reconstruire l'état du système à n'importe quel moment dans le passé. "Quel était le solde de ce compte le 15 février ?" — filtrez les événements jusqu'à cette date et rejouez.
Nouvelles projections
Besoin d'un nouveau rapport ? D'une nouvelle vue ? Rejouez tous les événements avec une nouvelle logique de projection. Pas besoin de migration de données.
Les Snapshots : optimiser la reconstruction
Rejouer 10 000 événements pour reconstruire un état est lent. La solution : les snapshots — des captures périodiques de l'état.
Événements : [1] [2] [3] ... [5000] [SNAPSHOT: solde=8420] [5001] [5002] ... [5050]
Pour reconstruire l'état actuel :
1. Charger le snapshot (version 5000)
2. Rejouer seulement les événements 5001 à 5050
Au lieu de 5050 événements, vous n'en rejouez que 50.
Event Sourcing + CQRS
L'Event Sourcing se combine naturellement avec CQRS. Les événements alimentent le côté écriture (la source de vérité), et des projections construisent des vues optimisées pour la lecture.
Commande → Aggregate → Événement → Event Store
│
┌─────────┼─────────┐
▼ ▼ ▼
Projection Projection Projection
"Soldes" "Relevé" "Stats"
│ │ │
▼ ▼ ▼
Read DB Read DB Read DB
Chaque projection lit les événements et construit une vue dénormalisée adaptée à un besoin de lecture spécifique.
Quand utiliser l'Event Sourcing
| Situation | Event Sourcing ? |
|---|---|
| Audit réglementaire obligatoire | Oui |
| Systèmes financiers (transactions, paiements) | Oui |
| Besoin de replay ou voyage dans le temps | Oui |
| Domaine métier complexe avec beaucoup de règles | Oui |
| Application CRUD simple | Non |
| Équipe peu expérimentée | Non |
| Données à faible durée de vie (cache, sessions) | Non |
Les difficultés
La complexité. L'Event Sourcing ajoute une couche d'abstraction significative. Le modèle mental est différent du CRUD classique. L'équipe doit être prête à investir dans cette courbe d'apprentissage.
L'évolution du schéma. Que se passe-t-il quand la structure d'un événement change ? Les anciens événements sont immuables — vous ne pouvez pas les modifier. Il faut gérer le versioning des événements : upcasting, événements de migration.
La cohérence éventuelle. Les projections sont mises à jour de manière asynchrone. Il y a un délai entre l'écriture d'un événement et sa visibilité dans les vues de lecture.
Le volume de données. Un système actif génère des millions d'événements. Le stockage, l'indexation et la performance de replay doivent être planifiés.
L'Event Sourcing change la façon dont vous pensez le stockage : l'historique est la source de vérité, l'état actuel n'en est qu'une projection. Cette approche donne un audit complet, la capacité de rejouer et de débugger, et la flexibilité de créer de nouvelles vues à tout moment. Mais c'est un investissement — réservez-le aux domaines où la traçabilité et le replay justifient la complexité supplémentaire.