CQRS + Event Sourcing — 00 — Pourquoi séparer lecture et écriture

Introduction à CQRS et Event Sourcing : définitions, philosophie, quand les utiliser et quand les éviter. Concepts indépendants de l'infrastructure (PostgreSQL, GCP, AWS, Firebase).

00 — Pourquoi séparer lecture et écriture

Ce que tu vas apprendre

  • Ce que CQRS résout que l'architecture classique ne résout pas
  • La différence entre CQRS et Event Sourcing — et pourquoi ce ne sont pas la même chose
  • Les cas où ces patterns valent le coût, et ceux où ils ne valent pas
  • Comment l'infrastructure change l'implémentation sans changer les concepts

Le problème que CQRS résout

Dans une architecture CRUD classique, le même modèle sert à la fois pour lire et écrire :

typescriptclass OrderRepository {
  async findById(id: string): Promise<Order> { ... }      // Lecture
  async save(order: Order): Promise<void> { ... }         // Écriture
  async findByUser(userId: string): Promise<Order[]> { ... } // Lecture
  async findWithItems(id: string): Promise<OrderWithItems> { ... } // Lecture complexe
}

Ça fonctionne tant que les besoins de lecture et d'écriture sont simples et similaires. Quand ils divergent, les tensions apparaissent :

  • Écrire une commande nécessite de charger l'agrégat complet pour vérifier les invariants (stock disponible, limite de crédit, statut valide)
  • Lire une commande pour l'afficher dans une liste nécessite souvent des données de 4-5 tables jointes, avec des champs calculés, des agrégations

Le même modèle Order ne peut pas être optimal pour les deux. Il finit par être un compromis médiocre pour chacun.

CQRS n'est pas une architecture compliquée — c'est la reconnaissance que lire et écrire sont deux problèmes différents.

Greg Young, qui a formalisé le pattern en 2010, décrit CQRS comme "une conséquence naturelle de l'application des principes DDD."


CQRS : la définition exacte

CQRS signifie Command Query Responsibility Segregation. Le principe :

  • Command : une intention de modifier l'état. CreateOrder, CancelOrder, AddItemToCart. Ne retourne rien (ou un ID).
  • Query : une demande de lecture. GetOrder, ListUserOrders, GetOrderSummary. Ne modifie rien.

Ces deux responsabilités ont des modèles séparés :

typescript// Write side — chargé de la logique métier et des invariants
class OrderCommandHandler {
  async handle(cmd: CreateOrderCommand): Promise<void> {
    // Charge l'agrégat, vérifie les invariants, sauvegarde
  }
}

// Read side — chargé de retourner des données optimisées pour l'affichage
class OrderQueryHandler {
  async handle(query: GetOrderQuery): Promise<OrderDTO> {
    // Requête optimisée directement sur la BDD, jointures, projections
  }
}

C'est tout. CQRS sans Event Sourcing, c'est juste ça : deux modèles, deux chemins.

Commands Queries Séparation
Modifient l'état Lisent l'état Modèles distincts

Event Sourcing : la définition exacte

Event Sourcing est un pattern de persistance, indépendant de CQRS.

Au lieu de stocker l'état courant d'un objet, tu stockes la séquence des événements qui ont mené à cet état.

// État courant (CRUD classique) — une ligne en BDD
{ id: "order-1", status: "shipped", total: 150, updatedAt: "2026-06-01" }

// Event Sourcing — la séquence d'événements
[
  { type: "OrderCreated",   data: { total: 150 },    version: 1 },
  { type: "OrderConfirmed", data: { confirmedAt: "..." }, version: 2 },
  { type: "OrderShipped",   data: { trackingId: "..." }, version: 3 },
]

L'état courant est reconstitué en rejouant ces événements.

Pourquoi c'est puissant

Tu ne perds jamais l'historique. Avec le CRUD, quand une commande passe de pending à shipped, l'état intermédiaire (confirmed) disparaît. Avec l'Event Sourcing, tout est là.

Ce qui permet :

  • Audit log gratuit — chaque changement est un événement enregistré
  • Debug : rejouer la séquence qui a mené à un bug
  • Nouvelles projections : créer une nouvelle vue des données à partir des événements passés, rétroactivement

CQRS ≠ Event Sourcing

Ces deux patterns sont souvent présentés ensemble parce qu'ils se complètent bien, mais ils sont indépendants :

CQRS Event Sourcing
Concerne L'organisation du code (commandes vs requêtes) La persistance (events vs état courant)
Infra requise Aucune — une seule BDD suffit Un event store (PostgreSQL, EventStoreDB)
Peut être utilisé seul Oui Oui (sans CQRS)
Combinés Le plus courant en production Naturel : les commands produisent des events

Dans cette série, on utilise les deux ensemble — c'est la combinaison la plus courante — mais on gardera PostgreSQL comme event store. Pas de Kafka, pas d'EventStoreDB.


Quand utiliser ces patterns

CQRS vaut le coup quand

  • Les besoins de lecture et d'écriture divergent significativement (modèles différents, volumes différents)
  • Tu as des rapports ou des vues complexes qui nécessitent des requêtes optimisées séparément
  • Le domaine a une logique métier riche (invariants, règles, workflows)

Event Sourcing vaut le coup quand

  • L'historique complet a de la valeur (finance, santé, audit légal, commandes e-commerce)
  • Tu as besoin de rejouer des événements pour corriger des bugs ou ajouter de nouvelles projections
  • Le débogage de l'état courant est difficile — "comment est-on arrivé là ?"

Quand ne pas les utiliser

Martin Fowler, qui a documenté les deux patterns, est direct là-dessus :

"CQRS is a pattern that I see frequently misused. [...] For most systems CQRS adds risk and complexity for no benefit." — Martin Fowler, CQRS (2011), martinfowler.com

Ne pas utiliser CQRS + Event Sourcing pour :

  • Un CRUD simple (blog, catalogue produits sans logique métier complexe)
  • Une équipe qui découvre le DDD — trop de complexité accidentelle
  • Des domaines où l'historique n'a pas de valeur

L'infrastructure ne change pas les concepts

Un point important avant de continuer : CQRS et Event Sourcing sont des patterns de conception, pas des choix d'infrastructure. Les concepts — commands, events, projections, streams — s'appliquent quelle que soit la stack.

Ce qui change selon l'infrastructure :

PostgreSQL seul PostgreSQL + réplicas GCP (Pub/Sub) Firebase AWS (EventBridge)
Event store Table events Table sur primary Cloud SQL ou Firestore Firestore (limité) DynamoDB
Projections Synchrones Éventuelle (lag) Async via Pub/Sub Cloud Functions Lambda
Cohérence Forte Éventuelle Éventuelle Éventuelle Éventuelle
Idempotence ON CONFLICT ON CONFLICT Critique (at-least-once) Critique Critique

Les articles 01 à 05 couvrent les concepts avec PostgreSQL comme référence — c'est la stack la plus directe pour comprendre les mécanismes. L'article 06 couvre le passage à chaque infrastructure cloud.

Ce que cette série couvre

Un système de gestion de commandes en TypeScript et Python, avec :

  • L'interface abstraite EventStore et ses implémentations (PostgreSQL, Firestore, DynamoDB)
  • Des command handlers qui produisent des domain events
  • Des projections synchrones et asynchrones
  • La reconstruction d'état par replay et snapshots
  • Le guide de choix d'infrastructure
Article Contenu
00 — Introduction Définitions, philosophie, quand utiliser
01 — Commands et events Handlers, domain events, validation
02 — Event store : interface et implémentations PostgreSQL, Firestore, DynamoDB
03 — Projections : synchrones et asynchrones Read models, Pub/Sub, eventual consistency
04 — Replay et snapshots Reconstruction d'état, performance
05 — Projet réel Système complet TypeScript + Python
06 — Choisir son infrastructure PostgreSQL, GCP, AWS, Firebase — guide de décision

Étape suivante : 01 — Commands et events


Sources

  • Young, G. (2010). CQRS Documents. cqrs.files.wordpress.com.
  • Fowler, M. (2011). CQRS. martinfowler.com.
  • Fowler, M. (2005). Event Sourcing. martinfowler.com.
  • Evans, E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley.
  • Vernon, V. (2013). Implementing Domain-Driven Design. Addison-Wesley.
  • Richardson, C. (2019). Microservices Patterns. Manning.

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.