Aller au contenu principal

Systèmes distribués · L3 · Section 7/12

Event sourcing et CQRS

Progression

Points d’expérience : XPSérie de jours consécutifs : · —Progression du module : — / —compris

#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

Commande
Intention de modifier l'état
Validation
Vérifier les invariants
Événement
Fait passé, immuable
Projection
Vue dérivée pour la lecture

#É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.

typescripttypescript

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.

Client
Command Handler
Event Store
Projection
Query Handler
1. WithdrawMoney
2. MoneyWithdrawn event
3. Event notification
4. Update balance view
5. GetBalance
6. Balance: 100€

#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.

typescripttypescript

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

code

1┌─────────────┐     ┌─────────────┐     ┌─────────────┐2│  Commands   │────▶│   Domain    │────▶│ Event Store │3└─────────────┘     │   Logic     │     └──────┬──────┘4                    └─────────────┘            │56┌─────────────┐     ┌─────────────┐     ┌─────────────┐7│   Queries   │◀────│ Projections │◀────│ Event Bus   │8└─────────────┘     └─────────────┘     └─────────────┘

#Quiz

Pourquoi les projections en CQRS sont-elles 'eventually consistent'?
Pourquoi les projections en CQRS sont-elles 'eventually consistent'?