Skip to main content
CQRS and Event Sourcing
  1. Posts/

CQRS and Event Sourcing

·676 words·4 mins·
Roman
Author
Roman
Photographer with MSci in Computer Science and a Home Lab obsession
Table of Contents

In most systems, reads and writes don’t scale the same way, and they rarely need the same data shape. As domains grow more complex, forcing a single model to handle both creates bloated entities and scaling bottlenecks. This article covers two patterns that address this directly: CQRS, which separates the write and read paths, and Event Sourcing, which replaces mutable state with an append-only log of what happened.

CQRS
#

Command Query Responsibility Segregation

Separate the write model (commands) from the read model (queries). Instead of one service/entity/repository doing both reads and writes, you split them into two distinct paths.


Problem with shared model CQRS solution
Read and write traffic scale differently Can optimize or scale each side independently
Business logic bleeds into read paths Write path owns rules; read path is pure data retrieval
Entities become bloated trying to serve both needs Focused, single-purpose classes on each side

Command (Writes)
#

  • Receives a command object, a simple DTO/record describing the intent
  • Passes through validation and business rules
  • Mutates state and persists changes

Query (Reads)
#

  • Returns DTOs shaped for the consumer, no domain objects leak out
  • No business logic, no side effects
  • Free to use optimized queries, projections, caching

Levels of Separation
#

Level 1 - Logical (same DB)
#

CQRS
  • Separate command/query services and DTOs
  • Same database, same tables
  • Minimal cost, just code organization
  • This is where most teams should start and often stay

Level 2 - Separate data stores (DB)
#

CQRS
  • Write side - persists to the source-of-truth store (relational DB for ACID guarantees); publishes change events
  • Read side - consumes events projected into a read store chosen to fit the query pattern (Elasticsearch for search optimized)
  • Eventual consistency - reads may lag behind writes
  • Significant operational complexity

Level 3 - Event Sourcing + CQRS
#

CQRS with Event Sourcing
  • Write side - appends immutable events to an event store (no mutable state, no UPDATE/DELETE)
  • Read side - consumes the event stream and builds purpose-built projections (read models)
  • Hydration replaces direct DB reads on the write side, aggregate state is rebuilt from its events before each command
  • Full audit log, time travel for free

When to Use
#

Use CQRS when
  • Read and write models diverge significantly
  • Different consumers need different views of the same data
  • Complex domain logic on the write side
  • Read/write traffic has very different scaling needs
Skip CQRS when
  • Simple CRUD with straightforward entities
  • Read and write shapes are nearly identical

Event Sourcing
#

  • Store every state change as an immutable event instead of just the current state
  • Events are appended to an event log (event store) in order, never updated or deleted
  • Current state is derived by replaying events from the beginning (or from a snapshot)
CQRS with Event Sourcing

  • Event Store: append-only log of all events, acts as the source of truth
  • Projection/view: a read model built by processing events (e.g. current account balance)
  • Snapshot: periodic checkpoint of state to avoid replaying the entire log on every read
  • Hydration (Rehydration): load one aggregate’s events and apply them in memory to reconstruct current state
    • Write-side (typical): reconstruct aggregate state before handling a command; happens on every incoming command
  • Replay: process events across many aggregates to build or rebuild a projection from scratch
    • Read-side (typical): rebuild or create projections on schema changes or for new consumers
  • Full audit trail - every change is recorded with who did what and when
  • Time travel - reconstruct state at any point in the past
  • Decoupling - consumers can build their own read models from the same event stream
  • Works naturally with event-driven architectures
  • Querying current state is indirect - requires projections or snapshots
  • Event schema evolution is hard - old events must stay readable as the schema changes
  • Storage grows indefinitely - events are never deleted
  • Increased complexity vs. simple CRUD
Often paired with CQRS

Event sourcing is commonly used with CQRS: the write side appends events, the read side builds optimized projections. This separates the complexity of event replay from the simplicity of read queries.

Use Cases
  • Banking / Finance: immutable transaction ledger
  • Audit-heavy systems: compliance, healthcare records