rgoussu@goussu: ~/library/applicative-architecture
~/library/applicative-architecture cat cqrs-and-event-sourcing.md

CQRS & event sourcing

# Separating writes from reads, and storing state as a log of events — two distinct patterns, their real costs, and when each earns its keep.

Conceptsaved 2026-08-08 #cqrs#event-sourcing#ddd#architecture#events

Overview

CQRS splits a system's write model (commands, invariants) from its read model (queries, projections); event sourcing stores state as the append-only sequence of domain events that produced it, rather than the current snapshot. They compose naturally — events feed the projections — but they are separate decisions with separate costs, and the classic mistake is adopting both wholesale when a modest read-model split (or neither) would do.

Key points

  • CQRS in essence: commands go through the domain model that enforces invariants; queries hit purpose-built read models (denormalized tables, search indexes, caches) updated from the write side. It can be as light as "separate query services over the same database" — the full two-store version buys scalability and pays with eventual consistency between write and read.
  • Event sourcing in essence: the event store is the source of truth; current state is a left-fold over events, with snapshots as an optimization. You gain a complete audit trail, temporal queries ("state as of March"), and retroactive new projections — capabilities a CRUD store simply cannot offer.
  • The real costs: event schema evolution (versioning, upcasters — events are forever), eventual consistency surfacing in the UX (read-your-own-writes needs explicit handling), projection rebuild operations, and a steep unfamiliarity tax on the team. These dominate the decision more than any technical limit.
  • Design anchors: events are facts, past tense, in domain language (OrderPlaced, not OrderUpdated); aggregates (DDD) define the consistency boundary a command sees; idempotent projections because events will be redelivered.
  • When it earns its keep: audit-critical domains (finance, healthcare), genuinely divergent read/write shapes or loads, temporal/behavioral analytics as a feature. When it doesn't: CRUD with symmetrical reads and writes — which is most software.
  • Don't confuse with event-driven integration: publishing integration events between services is a messaging pattern; event sourcing is a persistence pattern. A system can do the former without the latter — most should.
  • To explore: event stores (EventStoreDB, Postgres-as-event-store, Kafka's fit and misfit), Axon/eventuate frameworks, process managers vs. sagas atop event streams.

Practice

  • Banking kata, event-sourced twist (source) — warm-up: store deposits and withdrawals as events and derive the printed statement as a projection — a left-fold in miniature.
  • SimpleCQRS (m-r) (source) — Greg Young's reference implementation; read it, then port it to your language without peeking.
  • Event-sourced bank account (exercise) — the full build from scratch: aggregate, event store with optimistic concurrency, idempotent projections, and the eventual-consistency UX handled honestly.

Related