Event Sourcing
Pattern architectural stockant l'état applicatif comme séquence immuable d'événements, permettant audit complet et reconstruction temporelle.
Mis à jour le 9 janvier 2026
L'Event Sourcing est un pattern architectural qui consiste à persister l'état d'une application non pas sous forme d'instantanés (snapshots), mais comme une séquence chronologique d'événements immuables. Chaque changement d'état devient un événement métier explicite, créant une source de vérité historique complète. Cette approche révolutionne la traçabilité, l'audit et la capacité à reconstruire l'état du système à n'importe quel moment.
Fondements
- Les événements sont immuables et ajoutés uniquement (append-only), jamais modifiés ou supprimés
- L'état actuel est dérivé en rejouant (replay) la séquence complète d'événements
- Chaque événement capture un fait métier avec son contexte temporel et ses données
- Les événements forment le journal autoritaire (event store) de toutes les transactions système
Avantages
- Traçabilité parfaite : audit trail complet de toutes les modifications avec contexte métier
- Temporal queries : reconstruction de l'état système à n'importe quel point dans le passé
- Debugging facilité : reproduction exacte des scénarios en rejouant les événements
- Scalabilité en lecture : projections multiples optimisées pour différents cas d'usage
- Résilience : capacité de reconstruire intégralement le système depuis les événements
- Business intelligence : analyse rétroactive avec nouvelles projections sur données historiques
Exemple concret
Implémentation d'un système bancaire avec Event Sourcing, où chaque transaction devient un événement persisté :
// Définition des événements métier
interface DomainEvent {
eventId: string;
aggregateId: string;
timestamp: Date;
version: number;
}
interface AccountCreated extends DomainEvent {
type: 'AccountCreated';
initialBalance: number;
currency: string;
}
interface MoneyDeposited extends DomainEvent {
type: 'MoneyDeposited';
amount: number;
source: string;
}
interface MoneyWithdrawn extends DomainEvent {
type: 'MoneyWithdrawn';
amount: number;
destination: string;
}
type AccountEvent = AccountCreated | MoneyDeposited | MoneyWithdrawn;
// Aggregate reconstruit depuis les événements
class BankAccount {
private id: string;
private balance: number = 0;
private version: number = 0;
private uncommittedEvents: AccountEvent[] = [];
constructor(id: string) {
this.id = id;
}
// Reconstruction depuis l'event store
loadFromHistory(events: AccountEvent[]): void {
events.forEach(event => this.apply(event, false));
}
// Commandes métier
create(initialBalance: number, currency: string): void {
const event: AccountCreated = {
type: 'AccountCreated',
eventId: crypto.randomUUID(),
aggregateId: this.id,
timestamp: new Date(),
version: this.version + 1,
initialBalance,
currency
};
this.apply(event, true);
}
deposit(amount: number, source: string): void {
if (amount <= 0) throw new Error('Invalid amount');
const event: MoneyDeposited = {
type: 'MoneyDeposited',
eventId: crypto.randomUUID(),
aggregateId: this.id,
timestamp: new Date(),
version: this.version + 1,
amount,
source
};
this.apply(event, true);
}
withdraw(amount: number, destination: string): void {
if (amount <= 0) throw new Error('Invalid amount');
if (this.balance < amount) throw new Error('Insufficient funds');
const event: MoneyWithdrawn = {
type: 'MoneyWithdrawn',
eventId: crypto.randomUUID(),
aggregateId: this.id,
timestamp: new Date(),
version: this.version + 1,
amount,
destination
};
this.apply(event, true);
}
// Application des événements sur l'état
private apply(event: AccountEvent, isNew: boolean): void {
switch (event.type) {
case 'AccountCreated':
this.balance = event.initialBalance;
break;
case 'MoneyDeposited':
this.balance += event.amount;
break;
case 'MoneyWithdrawn':
this.balance -= event.amount;
break;
}
this.version = event.version;
if (isNew) this.uncommittedEvents.push(event);
}
getUncommittedEvents(): AccountEvent[] {
return [...this.uncommittedEvents];
}
markEventsAsCommitted(): void {
this.uncommittedEvents = [];
}
getBalance(): number {
return this.balance;
}
}
// Event Store simplifié
class EventStore {
private events: Map<string, AccountEvent[]> = new Map();
async save(aggregateId: string, events: AccountEvent[]): Promise<void> {
const existing = this.events.get(aggregateId) || [];
this.events.set(aggregateId, [...existing, ...events]);
}
async getEvents(aggregateId: string): Promise<AccountEvent[]> {
return this.events.get(aggregateId) || [];
}
// Temporal query : état à un moment donné
async getEventsUntil(aggregateId: string, date: Date): Promise<AccountEvent[]> {
const allEvents = await this.getEvents(aggregateId);
return allEvents.filter(e => e.timestamp <= date);
}
}
// Utilisation
const eventStore = new EventStore();
const account = new BankAccount('ACC-001');
// Création et opérations
account.create(1000, 'EUR');
account.deposit(500, 'Salary');
account.withdraw(200, 'Rent');
// Sauvegarde des événements
await eventStore.save('ACC-001', account.getUncommittedEvents());
account.markEventsAsCommitted();
// Reconstruction depuis l'historique
const accountCopy = new BankAccount('ACC-001');
const history = await eventStore.getEvents('ACC-001');
accountCopy.loadFromHistory(history);
console.log(accountCopy.getBalance()); // 1300Mise en œuvre
- Identifier les événements métier significatifs et concevoir leur schéma (event schema)
- Choisir un event store adapté (EventStoreDB, Kafka, PostgreSQL avec append-only tables)
- Implémenter les aggregates avec logique de reconstruction depuis événements (event replay)
- Créer des projections (read models) optimisées pour les différents cas de lecture
- Mettre en place des snapshots périodiques pour optimiser la reconstruction d'aggregates volumineux
- Implémenter la gestion des versions d'événements et des stratégies d'upcasting
- Configurer la réplication et sauvegarde de l'event store (données critiques)
- Développer des outils d'analyse et visualisation de l'historique événementiel
Conseil Pro
Combinez Event Sourcing avec CQRS pour séparer opérations d'écriture (événements) et lecture (projections). Utilisez snapshots tous les N événements pour éviter de rejouer des millions d'événements. Versionnez vos schémas d'événements dès le départ.
Outils associés
- EventStoreDB : base de données spécialisée pour Event Sourcing avec projections natives
- Apache Kafka : plateforme de streaming distribuée souvent utilisée comme event store
- Axon Framework : framework Java complet pour CQRS et Event Sourcing
- Marten : bibliothèque .NET pour Event Sourcing sur PostgreSQL
- Eventuous : framework Event Sourcing moderne pour .NET
- Lagom : framework Scala/Java pour microservices event-sourced
L'Event Sourcing transforme la gestion des données en privilégiant l'historique complet sur les instantanés. Pour les organisations nécessitant traçabilité complète, conformité réglementaire stricte ou analyse temporelle, ce pattern offre des garanties impossibles avec des architectures traditionnelles.
