Hermes Wiki
Architecture/Fundamentals/event-sourcing-and-cqrs

Event Sourcing and CQRS

Concept

Two distinct patterns that are frequently paired but solve different problems:

  • CQRS (Command Query Responsibility Segregation) — separates the model used to write data (commands) from the model used to read it (queries), instead of using one shared model for both. The write model can be normalized and validation-heavy; the read model can be a denormalized, pre-joined shape optimized purely for the queries the UI actually needs.
  • Event Sourcing — instead of storing only the current state of an entity, the system stores the full history of state-changing events (OrderPlaced, OrderShipped, OrderCancelled) as the source of truth. Current state is derived by replaying events, not read directly from a row that gets overwritten on every update.

They're often used together — the event store becomes the write side of a CQRS split, and one or more read-optimized "projections" are built by replaying events into whatever shape each query needs — but CQRS doesn't require events (you can split read/write models over a plain relational database), and event sourcing doesn't require CQRS (you could rebuild current state from events without a separate read model).

Tradeoffs

Aspect Cost Benefit
CQRS Two models to keep in sync (or accept eventual consistency between them) Read model can be shaped and scaled entirely independently of write constraints
Event Sourcing Every state transition needs an explicit event; replaying a long history to get current state gets slower over time without snapshotting Full audit trail for free; can answer "what did this look like at time T" and rebuild any read model retroactively
Combined Meaningfully more moving parts (event store, projections, eventual consistency between them) than a CRUD table Fits domains where audit history and flexible reporting shapes both matter

The shared cost across both is added complexity for a benefit (independent read scaling, full history) that a large fraction of systems simply don't need — this is one of the most commonly over-applied pattern pairs in the industry, adopted for its architectural appeal rather than a genuine requirement.

When to use / when not to

  • Use when the domain has a genuine audit/compliance requirement to reconstruct history (financial ledgers, order lifecycles, regulatory trails) — the append-only event log is a natural fit for "prove what happened and when."
  • Use CQRS alone (without event sourcing) when read and write load patterns diverge sharply — heavy write validation but very different, high-volume read shapes — without needing full history replay.
  • Don't reach for either as a default for a standard CRUD service. If "what changed and when" was never a real requirement and read/write patterns aren't meaningfully different, a normal table with an updated_at column is simpler, cheaper, and easier for the next engineer to reason about.
  • Don't adopt event sourcing purely for the audit trail if a simpler append-only audit log table alongside a normal CRUD model would satisfy the same compliance requirement without the replay/projection machinery.

Common pitfall

Underestimating eventual consistency between the write side (event store) and the read-side projections. A command that just succeeded may not yet be reflected in the read model the UI queries immediately after — a user submits an order and refreshes the order list before the projection catches up, sees nothing, and assumes the submission failed. Systems that adopt CQRS/event sourcing without designing for this lag (read-your-writes guarantees, optimistic UI updates, or accepting and communicating the staleness window) turn an architectural benefit into a confusing user-facing bug.

Principal Engineer Lens

The Principal-level signal here is restraint, not familiarity with the pattern. Anyone can describe CQRS and event sourcing; the harder and more valuable skill is correctly identifying when a system's actual requirements (audit trail, divergent read/write shapes) justify the complexity versus when it's pattern-matching for its own sake. Being the voice in a design review that says "we don't actually need full event sourcing here, a normal table plus an audit log table gets us the same compliance answer for a fraction of the operational cost" is exactly the kind of judgment call that distinguishes senior architectural thinking from resume-driven design.

Reel Script

Setup: Say you're designing an order-management system and someone in the review asks "can we prove exactly what happened to this order, in what order, six months from now?" A normal CRUD table that just overwrites the current status can't answer that — this is the exact problem event sourcing solves.

Concept walkthrough: Explain event sourcing first: instead of storing current state, store every state-changing event and derive current state by replaying them. Then CQRS: split the write model from the read model entirely, so the read side can be shaped purely for the queries the UI needs, independent of the write side's structure.

Real example tie-in: Walk through an order lifecycle — OrderPlaced, PaymentAuthorized, OrderShipped — as an event stream, then show how a read-side projection turns that into a simple "current order status" row for the UI, while the full event log stays available for an audit or a dispute.

Tradeoffs & alternatives: Be honest about the added complexity — event store, projections, eventual consistency between them — versus a plain CRUD table with an audit log column. Most systems don't actually need the full pattern; a lot of "do we need event sourcing" conversations end with "no, we just need an audit trail."

Principal Engineer takeaway: The valuable move isn't reciting the pattern, it's correctly saying no to it when the requirements don't justify it — restraint reads as more senior than reaching for the architecturally impressive answer by default.

Sources:

Hermes Wiki