Software ArchitectureSystem Design

CQRS + Event Sourcing: A Practical Guide to Scalable Data Architectures

TT
TopicTrick Team
CQRS + Event Sourcing: A Practical Guide to Scalable Data Architectures

CQRS + Event Sourcing: A Practical Guide to Scalable Data Architectures


Table of Contents


Why CRUD Fails at Scale

Standard CRUD (Create, Read, Update, Delete) uses a single model for all data operations:

sql

Problems at scale:

  1. Lock contention: Write locks on users block reads during updates
  2. Index bloat: Adding indexes to speed reads slows writes
  3. Schema coupling: The table must serve all query shapes (dashboard, API, reports)
  4. Audit limitations: You can see current state, but not history of changes

CQRS: The Core Concept

mermaid

The fundamental insight: commands and queries have completely different access patterns and optimization requirements. Stop fighting over the same data model.


Command Side: The Write Model

The command side validates business rules and records what happened as an immutable event:

java

Event Sourcing: Storing Events Not State

In a traditional system, you store current state: orders.status = 'SHIPPED'.

In Event Sourcing, you store the history of facts:

json

To get current state: Replay all events:

java

The superpowers this gives you:

FeatureCRUDEvent Sourcing
Audit logAdd extra audit table (often forgotten)Built-in — every change is an event
Time travelCannot see past statesReplay up to any point in time
Debug production"Why does this record look like this?"Read the event history — the entire story
Business analyticsAggregate from current snapshotsQuery event stream directly
UndoComplex compensating logicApply reverse event

Query Side: Building Read Models with Projectors

A Projector (also called Event Handler or Read Model Builder) listens to events and builds optimized views for reads:

java

Multiple read models from the same events:

  • OrderDashboardProjector → MongoDB for order management UI
  • OrderAnalyticsProjector → Elasticsearch for search and filtering
  • OrderRevenueProjector → Separate PostgreSQL table for finance reporting

The Outbox Pattern: Atomic Commands + Events

A critical challenge: how do you ensure the event is published to a message bus only if the command succeeded?

Problem: Two separate operations = potential inconsistency:

java

Outbox Pattern Solution: Write the event to an outbox table in the same database transaction, then relay it asynchronously:

java

Eventual Consistency: Handling the Delay

Between a command being accepted and the read model being updated, there's a short delay. REST APIs need to handle this gracefully:

java

When to Apply CQRS + Event Sourcing

Apply when you have:

  • Complex domain with many business rules that change state
  • Strict audit requirements (finance, healthcare, legal)
  • Read and write loads differ by 10:1 or more
  • Multiple downstream consumers need notifications of state changes
  • "What was the state at time T?" is a real business requirement

Do NOT apply for:

  • Simple CRUD apps (todo lists, basic CMS, admin dashboards)
  • Small teams without distributed systems experience
  • Greenfield projects where the domain is not yet understood
  • Systems without audit or compliance requirements

Frequently Asked Questions

Does CQRS always require Event Sourcing? No — they are complementary but independent patterns. You can use CQRS with a traditional relational database on the write side (just update the table and also update a separate read model). Event Sourcing provides the best write-side storage for CQRS, but it's not required. Many teams apply CQRS first, then introduce event sourcing as the auditing/replay requirements emerge.

What happens if a Projector crashes mid-update? Projectors should be idempotent — processing the same event twice produces the same result. Event stores assign each event a sequence number. Projectors track the last processed sequence number in their own store. On restart, they resume from where they stopped. This guarantees at-least-once processing, and idempotency ensures at-most-once effect.


Key Takeaway

CQRS + Event Sourcing is the architectural pattern for systems where data history, audit, and scale matter. The event log becomes the single source of truth — current state is just a materialized view. The complexity cost is real: you need to handle eventual consistency, design projectors, manage event schemas, and deal with the Outbox pattern. But for banking, healthcare, trading, and logistics systems — where the history of why data is in a state is as important as the state itself — there is no alternative that scales as elegantly.

Read next: Sharding Patterns: How to Scale Your Database to Terabytes →


Part of the Software Architecture Hub — comprehensive guides from architectural foundations to advanced distributed systems patterns.