MODULE 12 OF 6 · APPLIED

CQRS and Event Sourcing

35 min read 4 outcomes Interactive diagram

By the end of this module you will be able to:

  • Explain CQRS and why separating read and write models enables independent scaling
  • Define event sourcing and describe the append-only event store model
  • Explain why CQRS and event sourcing are independent patterns and when to use each
  • Apply the HMRC Making Tax Digital case to understand real-world CQRS trade-offs
Abstract data flow and financial processing systems (photo on Unsplash)

Real-world system · 2019 to present

HMRC's Making Tax Digital: 200 million tax events per year on one architecture.

HMRC's Making Tax Digital (MTD) programme, launched in 2019, requires UK businesses to submit VAT returns digitally and maintain digital records. The system handles over 200 million tax events per year. The write path, accepting VAT return submissions, requires strict ACID consistency and a permanent, tamper-evident audit trail. The read path, serving taxpayers viewing their account dashboard, requires low latency for millions of concurrent users.

A single database design cannot serve both needs equally. A normalised write model optimised for consistency creates complex join queries on the read side. A denormalised read model optimised for low-latency queries violates write-side consistency requirements. HMRC's MTD architecture separates the command (write) model from the query (read) model, with event sourcing providing the authoritative, tamper-evident audit trail.

The event store serves as the permanent record of every tax event. The read model is a denormalised projection, rebuilt from the event store if it becomes inconsistent. This architecture is not unique to HMRC: it is the same pattern used by financial trading platforms, healthcare records systems, and any domain where audit history is a core regulatory requirement rather than a supplementary logging concern.

When the same system must guarantee audit accuracy for HMRC and serve dashboards to 2 million concurrent users, can one database design serve both needs equally well?

With the learning outcomes established, this module begins by examining cqrs: the origin and the principle in depth.

12.1 CQRS: the origin and the principle

Bertrand Meyer stated the underlying principle in 1988: every method should be either a command (which changes state and returns nothing) or a query (which returns data and changes nothing), but not both. Greg Young applied this principle at the architectural level in 2010, introducing the term CQRS: Command Query Responsibility Segregation.

In CQRS, the system has two distinct models. The command model (write side) handles state changes through commands: PlaceOrder,SubmitVATReturn, CancelReservation. It enforces business rules and invariants. The query model (read side) handles data retrieval optimised for the specific read patterns the system requires.

At the simplest level, CQRS means different code paths in the same service with the same database. Command handlers process writes; query handlers process reads. This alone improves code clarity. The more powerful form uses a dedicated read store, a denormalised database projection optimised for specific query patterns and updated asynchronously from domain events emitted by the write side.

CQRS is simply the creation of two objects where there was previously only one. The separation occurs based upon whether the methods are a command or a query.

Greg Young - CQRS Documents. cqrs.nu, 2010

Young's original formulation emphasises the simplicity of the split. CQRS is not a complex pattern - it is a deliberate separation. The complexity comes from the consequences of that separation: separate stores introduce eventual consistency; event-sourced write models introduce projection complexity. Young himself recommends CQRS in fewer than 10% of bounded contexts.

With an understanding of cqrs: the origin and the principle in place, the discussion can now turn to read model optimisation and projections, which builds directly on these foundations.

12.2 Read model optimisation and projections

A read model (also called a projection or view model) is a denormalised data structure pre-computed for a specific query pattern. Rather than joining five tables on every request, the read model stores the joined and computed result, updated whenever the underlying data changes.

HMRC's taxpayer account dashboard shows: total VAT owed, breakdown by period, submission history, and upcoming deadlines. In a normalised write model, this requires joining the submissions table, the periods table, the obligations table, and computing aggregates. At 2 million concurrent users, this query load would be unsustainable on the write database.

The read model stores a pre-computed taxpayer summary document, updated each time a VATReturnSubmitted event is published. The query handler reads a single document per user. Latency drops from hundreds of milliseconds to single digits.

The trade-off is eventual consistency. Between the event being published and the read model being updated, the summary may show stale data. For most read patterns this is acceptable. For the user who just submitted a return and refreshes immediately, the system should either show a “processing” state or read recent writes directly from the command model.

Common misconception

CQRS always requires event sourcing.

CQRS and event sourcing are independent patterns. CQRS separates command and query handlers. Event sourcing changes the persistence model to an append-only event log. You can implement CQRS with a traditional relational database for both read and write sides. You can implement event sourcing in a system that uses a single command/query model. They are frequently combined because event sourcing naturally produces domain events that are ideal inputs to CQRS read projections, but neither requires the other.

With an understanding of read model optimisation and projections in place, the discussion can now turn to event sourcing: the event store as source of truth, which builds directly on these foundations.

Architecture whiteboard with event flow diagrams showing CQRS read and write model separation
CQRS separates read and write models, allowing each to be optimised independently. The write model enforces consistency; the read model optimises latency.

12.3 Event sourcing: the event store as source of truth

Traditional persistence stores current state: a row in the database holds the current order status. When state changes, the row is updated and the previous value is overwritten. History must be maintained separately in an audit log, if it exists at all.

Event sourcing stores the sequence of events that produced the current state as an immutable, append-only log. To determine the current state of order-1042, replay its events from the beginning: OrderCreated,ItemAdded (3 times), PaymentProcessed,OrderShipped. The current state is derived; the event log is the source of truth.

This model provides three properties that traditional persistence cannot. First, full audit history is built into the storage format: every state transition is preserved. Second, temporal queries: “what was the state of this account at 10:00 on 15 March?” is answered by replaying events to that timestamp. Third, projection rebuilding: if a read model becomes corrupted or a new query pattern is needed, replay the event log and rebuild from scratch.

As the event log grows, replaying all events for every state reconstruction becomes slow. Snapshots address this: periodically store a snapshot of the current aggregate state and replay only events since the snapshot. The snapshot is a performance optimisation; the event log remains the source of truth and could reconstruct the state from the beginning if required.

Event sourcing ensures that all changes to application state are stored as a sequence of events. Not just can we query these events, we can also use the event log to reconstruct past states, and as a foundation to automatically adjust the state with past events.

Martin Fowler - Event Sourcing. martinfowler.com, 2005

Fowler identifies three capabilities event sourcing provides that state-based persistence cannot: queryable history, reconstructable past states, and the ability to replay with different logic to produce different projections. HMRC's MTD audit trail depends on all three. The event store is not a supplement to the system; it is the system.

With an understanding of event sourcing: the event store as source of truth in place, the discussion can now turn to event store design and eventstoredb, which builds directly on these foundations.

Loading interactive component...

12.4 Event store design and EventStoreDB

An event store is an append-only database optimised for event streams. Events are organised into streams, each stream representing an aggregate (an order, a tax account, a trading position). Events are written to the stream end with an expected version for optimistic concurrency control: if two processes attempt to append event 101 to a stream that ends at event 100, one will succeed and one will receive a concurrency conflict error.

EventStoreDB (open source, maintained by Event Store Ltd) is purpose-built for event sourcing. It provides: stream-per-aggregate storage, subscriptions for real-time event delivery to projectors, server-side projections for cross-stream queries, and a catch-up subscription mechanism for building read models from any historical point.

PostgreSQL can be used as an event store with a table structured as:(stream_id, position, event_type, data, metadata, created_at). Appends use optimistic locking on position. Reads select all events for a stream ordered by position. This approach trades EventStoreDB's optimised features for operational simplicity if the team already operates PostgreSQL.

With an understanding of event store design and eventstoredb in place, the discussion can now turn to trade-offs and when not to use these patterns, which builds directly on these foundations.

Event log stream visualisation representing the append-only nature of event sourcing (photo on Unsplash)
The event store is an append-only log. Events are never updated or deleted. Current state is derived by replaying the event stream.

12.5 Trade-offs and when not to use these patterns

Greg Young has stated that CQRS is appropriate in fewer than 10% of bounded contexts in a typical system. Event sourcing applies to an even smaller fraction. Both patterns add significant accidental complexity in exchange for benefits that only materialise in specific scenarios.

CQRS adds operational complexity: separate read and write stores to maintain, event projectors to operate, and eventual consistency to handle in the user interface. These costs are justified when read and write loads are dramatically different, when multiple teams need to evolve read and write models independently, or when HMRC-style regulatory requirements mandate separate read and write optimisations.

Event sourcing adds: event schema management and evolution, snapshot strategy design, projection rebuilding when read models change, and a steeper learning curve for the development team. These costs are justified when full audit history is a core business or regulatory requirement, when temporal queries are needed, or when projections need to be rebuilt for new features. They are not justified for a standard CRUD application where history has no business value.

Common misconception

Event sourcing means storing events instead of state.

Event sourcing stores events AND derives current state via projections. You have both. The event log is the source of truth; the current state (aggregate) is a projection of those events loaded into memory when needed. Saying 'instead of state' misrepresents the model: state is always present, derived on demand from the immutable event log.

12.6 Check your understanding

In HMRC's Making Tax Digital system, a taxpayer submits a VAT return and immediately refreshes their account dashboard. The dashboard still shows the old total. What is the correct technical explanation and the correct design response?

A financial services firm stores only current account balances. A regulator requires a full 7-year transaction history. Which pattern directly solves this problem without retrofitting an external audit log?

A team is building a content management system for an internal marketing team: create, edit, and publish articles. The lead architect proposes using event sourcing for all entities. What is the most important question to ask before accepting this proposal?

Key takeaways

  • CQRS separates command handlers (writes) from query handlers (reads). At its simplest, this is different code paths in the same service. Separate read stores add optimisation at the cost of eventual consistency.
  • Event sourcing stores state as an immutable, append-only event log. Current state is a projection derived by replaying the log. The event log is the source of truth, not the derived state.
  • HMRC's Making Tax Digital uses CQRS with event sourcing to serve 200 million tax events per year: the write side enforces consistency, the read side serves concurrent users at low latency.
  • CQRS and event sourcing are independent patterns. CQRS can use a traditional database. Event sourcing can be used in a non-CQRS system. They complement each other but neither requires the other.
  • Apply these patterns only when specific pain points justify the complexity: regulatory audit requirements, temporal query needs, dramatically different read and write scaling requirements.

Standards and sources cited in this module

  1. Young, G. CQRS Documents. cqrs.nu, 2010.

    Full document collection

    The canonical formalisation of CQRS by its principal author. The 10% applicability guidance in Section 12.5 is from Young's writing. His definition in Section 12.1 is quoted directly.

  2. Fowler, M. Event Sourcing. martinfowler.com, 2005.

    Full article

    The definition of event sourcing and the three capabilities it provides (queryable history, reconstructable states, event replay) in Section 12.3 are from this article.

  3. HMRC. Making Tax Digital for VAT. gov.uk, 2019.

    Technical requirements and architecture guidance

    The primary source for the MTD architecture context in the opening story and Section 12.2. The 200 million events figure is from HMRC's published technical documentation.

  4. EventStoreDB documentation. eventstore.com.

    Getting started, projections, and clustering

    The primary reference for EventStoreDB design patterns in Section 12.4, including stream-per-aggregate, optimistic concurrency, and catch-up subscriptions.

  5. Fowler, M. CQRS. martinfowler.com, 2011.

    Full article

    Fowler's synthesis of CQRS, clarifying the pattern's relationship to event sourcing and the scenarios where each is and is not appropriate.

What comes next: CQRS separates read and write concerns. But where should the boundaries between services actually be drawn? Module 13 introduces Domain-Driven Design: bounded contexts, aggregates, ubiquitous language, and the context mapping patterns that describe how teams and services relate.

Module 12 of 22 in Applied