ES (Event Sourcing) and CQRS (Command Query Responsibility Segregation) are decisions about what form to keep the source of truth in, and how to derive views from it.

ES keeps the source of truth as a sequence of changes rather than state. The current state is derived from that sequence. CQRS separates distinct views from the same source of truth. The two patterns are independent but pair into one design. When a new read model is needed, a new projection consumes the same event sequence from the start.

flowchart LR
  A[Command] --> B[(Event Store
append-only)] B --> C[Projection] C --> D[Read Model
Search] C --> E[Read Model
Analytics] C --> F[Read Model
UI]

Event Sourcing

CRUD typically treats one thing as the source of truth: the current value in a row. If the orders table reads PAID, the order is paid. Changes are overwritten by UPDATE, so how it reached that state is lost.

ES replaces that with a sequence of changes. OrderCreated, PaymentRequested, PaymentCompleted, OrderShipped — events accumulate append-only, and that sequence is the source of truth. “Is the current order paid?” is answered by replaying events from the beginning. State is the result derived from the event sequence.

This small shift changes the system’s design premise.

  • An audit log follows naturally. Every change is already recorded as an event, so a separate audit infrastructure is unnecessary.
  • Time-travel debugging becomes possible. Replaying to a particular point reproduces the state at that point.
  • Adding a new view becomes free. A new projection over the event sequence yields a new read model.

The cost follows. As events accumulate, replaying from scratch every time grows expensive. That’s why snapshots are introduced — store the state up to a point in time and replay only events after it. Schema changes are awkward as well. An event published years ago is still the source of truth in the system, so its shape cannot be altered casually.

Kafka is often raised as an event store. The append-only log model aligns with ES. But Kafka’s retention policy typically deletes data after a window, which conflicts with ES’s premise of permanent retention of every event.

CQRS

CRUD handles both write and read with one model. In an order domain, a single Order carries validation, state changes, search, and analytics. The setup is clean in a small system but a single model struggles to serve both demands as it grows. The write side wants business rules and transactional integrity, while the read side wants fast queries and varied views, and a single model rarely serves both well.

CQRS accepts the asymmetry and separates the write model (Command side) from the read model (Query side). The write model is optimized for domain rules and consistency, the read model for query efficiency and representation. Both handle the same source of truth in different shapes.

The read model is not one but many. A search index, an analytics aggregate, a UI projection — each derived from the same source of truth. New screen, new read model.

Read-model update timing splits three ways.

  • Synchronous update — the read model updates in the same transaction as the write. Consistency is immediate but the write transaction bears more weight.
  • Asynchronous update — the write emits an event and the read model updates in a separate flow. Eventual consistency, and the most common pattern.
  • On-demand update — derived at read time. Suits views with low query frequency.

Asynchronous updates introduce staleness: for a short window after a write, the read model may return an old value. Whether that staleness is acceptable to the business decides whether CQRS is on the table.

Combining ES and CQRS

ES and CQRS are independent but pair well.

When ES treats the source of truth as an event sequence, CQRS’s read model takes that sequence as input. As events are appended, projections consume them and update the read model. The event is the read-side material, so the integration cost between the two patterns is small. A new read model means a new projection that replays events from the start.

This combination flows into the Saga and Outbox patterns as well. An ES event becomes a Saga trigger directly, and the “atomicity between DB write and event publication” that Outbox guarantees comes for free in ES — storing the event is storing the business data.

The downside: the source of truth (events) and views (read models) live in different stores, so read and write consistency is not immediate. A user may not see the result of their action on screen right away, which directly affects UX decisions.

Adoption Criteria

The decomposition criteria — domain boundary, data ownership, scale pattern, failure isolation — set the starting point for ES/CQRS adoption.

  • When the domain demands audit/compliance as a core requirement ES fits well. Finance, insurance, healthcare.
  • When data ownership requires multiple read-model shapes for one domain CQRS follows. Search, analytics, and a real-time dashboard each demanding a different view of the same source of truth.
  • When the scale pattern shows different load shapes for read and write CQRS read-model separation fits. A different kind of separation from a read replica — the models themselves differ.
  • When failure isolation disallows a read-side failure from blocking writes asynchronous read-model updates provide that isolation.

Not every system needs ES/CQRS. In a system where a single CRUD suffices, adopting ES spreads derivation cost across the system. The decomposition criterion sets what ES/CQRS can give, and whether that value justifies the cost is what I weigh before adopting.

Operational Cost

ES brings audit log, time travel, and freedom in adding views, but the cost of receiving those values is spread across the system.

  • Replay cost — without snapshots, every replay starts from the beginning. With snapshots, the snapshots themselves carry operational weight.
  • Projection operations — each read model’s update flow is managed separately, with reprocessing strategies on failure.
  • Schema change difficulty — past events cannot be altered freely, so versioning, upcasting, and weak/strong schema patterns become separate operational practice. Covered in a follow-up post.
  • Debugging abstraction — tracing consistency between event sequences and projections is harder than reading a single state model.

If audit, compliance, time-travel debugging, or freedom in adding views are not core values of the system, plain CRUD is often the right call. When the pattern itself becomes the goal of adoption, the system runs fine on the happy path while complexity accumulates without ever realizing ES’s value.

References

  • Microservices Architecture — Decomposition criteria (domain boundary, data ownership, scale, failure) and communication decisions. The premise behind judging the value of ES/CQRS adoption.
  • Distributed Transactions — Single-transaction decomposition and reassembly with Saga and Outbox. The point where ES events become Saga triggers directly.
  • Kafka Fundamentals and KRaft Mode — Kafka mechanics and retention policy. Background for understanding the premise gap with ES’s permanent retention.