Systèmes distribués · L3 · Section 7/12
Event sourcing et CQRS
Progression
#Event Sourcing et CQRS
Au lieu d'écraser l'état, on conserve la suite des événements qui y mènent. L'event sourcing facilite l'audit, le débogage et la reconstruction de l'état après incident.
#Event Sourcing: principe
#Événements vs État
Approche traditionnelle: on stocke l'état actuel. Si le solde est 100€, on ne sait pas comment on y est arrivé.
Event sourcing: on stocke les événements (Dépôt 50€, Retrait 20€, ...). L'état se reconstruit en rejouant les événements.
1// Événements (faits passés, immuables)2type AccountEvent =3 | { type: 'AccountOpened'; accountId: string; timestamp: Date }4 | { type: 'MoneyDeposited'; amount: number; timestamp: Date }5 | { type: 'MoneyWithdrawn'; amount: number; timestamp: Date }6 7// Reconstruction de l'état8function computeBalance(events: AccountEvent[]): number {9 return events.reduce((balance, event) => {10 switch (event.type) {11 case 'MoneyDeposited': return balance + event.amount12 case 'MoneyWithdrawn': return balance - event.amount13 default: return balance14 }#CQRS: Command Query Responsibility Segregation
CQRS sépare les chemins d'écriture (commandes) et de lecture (queries). On écrit via une interface qui valide les invariants, on lit via des vues optimisées.
#Pourquoi séparer?
Écritures: doivent valider les règles métier, garantir la cohérence.
Lectures: doivent être rapides, potentiellement dénormalisées.
En les séparant, on peut optimiser chaque chemin indépendamment.
1// Côté écriture: valider les invariants2function handleWithdraw(command: WithdrawCommand, state: AccountState): AccountEvent[] {3 if (state.balance < command.amount) {4 throw new Error('Insufficient funds')5 }6 return [{ type: 'MoneyWithdrawn', amount: command.amount, timestamp: new Date() }]7}8 9// Côté lecture: vue optimisée10interface AccountBalanceView {11 accountId: string12 balance: number13 lastUpdated: Date14}#Projections
Les projections transforment le flux d'événements en vues adaptées aux besoins de lecture. On peut avoir plusieurs projections du même flux.
Projection "Solde courant": un nombre par compte.
Projection "Historique": liste des transactions.
Projection "Alertes": comptes passés sous un seuil.
#Quand utiliser Event Sourcing?
Bons cas d'usage:
Systèmes financiers, comptabilité (audit obligatoire).
Domaines avec logique métier complexe évoluant dans le temps.
Systèmes nécessitant des reconstitutions temporelles ("time travel").
Mauvais cas d'usage:
CRUD simple sans besoin d'historique.
Systèmes à très haute fréquence d'écriture sans besoin de replay.
Équipes non familières avec le pattern (complexité accidentelle).
#Challenges
Taille du store: les événements s'accumulent. Solutions: snapshotting, archivage.
Schema evolution: les événements évoluent. Solutions: versionnement, upcasting.
Replay lent: reconstruire l'état de zéro peut prendre du temps. Solution: snapshots périodiques.
Complexité cognitive: différent du modèle mental CRUD habituel.
#Architecture typique
1┌─────────────┐ ┌─────────────┐ ┌─────────────┐2│ Commands │────▶│ Domain │────▶│ Event Store │3└─────────────┘ │ Logic │ └──────┬──────┘4 └─────────────┘ │5 ▼6┌─────────────┐ ┌─────────────┐ ┌─────────────┐7│ Queries │◀────│ Projections │◀────│ Event Bus │8└─────────────┘ └─────────────┘ └─────────────┘