Skip to content

Event Sourcing & CQRS — Detailed#

flowchart TB
  subgraph Write[Write side - command]
    C[Client]
    CMD[Command handler]
    AGG[Aggregate<br/>load events, decide, emit]
    ES[(Event store<br/>append-only)]
  end

  subgraph Bus[Event bus]
    KAFKA[Kafka / outbox]
  end

  subgraph Read[Read side - query]
    PROJ[Projector / consumer]
    RM1[(Read model 1<br/>SQL view)]
    RM2[(Read model 2<br/>Elasticsearch)]
    RM3[(Read model 3<br/>cache)]
    Q[Query API]
  end

  C --> CMD --> AGG
  AGG --> ES
  ES --> KAFKA
  KAFKA --> PROJ
  PROJ --> RM1
  PROJ --> RM2
  PROJ --> RM3
  C --> Q
  Q --> RM1
  Q --> RM2
  Q --> RM3

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class C client;
    class CMD,AGG,PROJ,Q service;
    class ES,RM1,RM2,RM3 datastore;
    class KAFKA queue;

Event sourcing — what + why#

  • Every state change = an immutable event appended to the store.
  • Current state = fold(events, initial).
  • Replay events to rebuild state at any point in time → free audit log + time travel.
Account a-42 events
  AccountOpened(owner=alice)
  Deposited(100)
  Deposited(50)
  Withdrew(30)
→ balance = 120

CQRS — what + why#

  • Command model: optimised for writes, enforces invariants.
  • Query model: many denormalised projections, optimised per use case.
  • Eventual consistency between them (often ms).

The two are independent ideas — you can do CQRS without event sourcing, and event sourcing without CQRS.

Aggregates + events#

sequenceDiagram
  participant C as Client
  participant H as Command Handler
  participant A as Aggregate
  participant E as Event Store
  C->>H: Withdraw(account=42, amt=30)
  H->>E: load events for 42
  E-->>H: [Opened, Deposited(100), Deposited(50)]
  H->>A: rebuild from events, then withdraw(30)
  A-->>H: ok + new event Withdrew(30)
  H->>E: append Withdrew(30) with expected version
  E-->>H: ack

Snapshots#

Replaying 50k events per command is expensive. Persist a snapshot every N events; start replay from there.

Projection strategies#

  • Synchronous in-tx: same DB; strong but couples write to all readers.
  • Async via outbox + bus (most common): scalable, eventually consistent.
  • On-demand: build a projection only when first queried.

Concurrency#

  • Optimistic concurrency on append: expectedVersion = N. Conflict → retry.
  • One aggregate = one consistency boundary; cross-aggregate ops go via sagas.

When to use#

  • Strict audit / regulatory replay (banking, ledger, trading).
  • Multiple read shapes over the same data.
  • Long-running business processes where history matters.

When NOT to use#

  • CRUD app with no audit need — adds complexity for nothing.
  • Heavy schema churn — replaying old events with new code is painful.
  • Reporting-only workloads — straight ETL is simpler.

Pitfalls#

  • Event versioning: rename a field? Upcasters or never-rename rules.
  • Replay performance: snapshots + idempotent projectors.
  • Read-after-write: projections lag — pin reads to write-DB for a session, or push from write API.

Glossary & fundamentals#

Concepts referenced in this design. Each row links to its canonical page; the tag column shows whether it is a high-level (HLD) or low-level (LLD) concept.

Tag Concept What it is Page
HLD Pub/Sub & message brokers topics, consumer groups, delivery semantics pub-sub-pattern
HLD CAP / PACELC C vs A under partition; L vs C otherwise cap-pacelc
HLD Idempotency & retries safe re-execution, backoff + jitter idempotency-retries
HLD Event sourcing + CQRS commands -> events; separate read model event-sourcing-cqrs
LLD Behavioural patterns Strategy, Observer, State, Command, Chain behavioral-patterns
LLD Immutability immutable types, persistent collections immutability