Event Sourcing and CQRS Integration

The architectural pattern that combines Event Sourcing (ES) as the write-side persistence model with CQRS (Command Query Responsibility Segregation) read-side projections — each pattern resolving the other’s primary weakness.

Problem

Event Sourcing alone: Storing state as an event log is powerful, but rebuilding current state by replaying all events is expensive. Complex read queries (reporting, filtering, search) against an event store are impractical.

CQRS alone: Separating read and write models is useful, but what populates the write model’s event log? CQRS without Event Sourcing typically still uses a traditional database for writes, losing the audit trail.

Combined: ES provides the authoritative write-side log; CQRS projections provide optimized read models built from that log.

Solution / Explanation

The Combined Flow

Command
   │
   ▼
Command Handler
   │
   ▼
Aggregate (ES write model)
   │ validates command
   │ applies domain event
   ▼
Event Store (append-only log)
   │
   ├──► Event Bus (Kafka / RabbitMQ)
   │
   ▼ (async)
Projection Engine / Event Handler
   │
   ├──► Read Model A (search optimized)
   ├──► Read Model B (reporting)
   └──► Read Model C (mobile view)
         │
         ▼
    Query Handler responds to read requests

Event Sourcing as the Write Side

  • Aggregates process commands, validate business rules, and emit domain events
  • Domain events are appended to the event store (immutable, append-only)
  • Current aggregate state is reconstructed by replaying events (or from a snapshot)
  • The event log is the single source of truth for the write side

CQRS Projections as the Read Side

  • Event handlers consume domain events from the event bus
  • Each projection builds a denormalized read model optimized for a specific query
  • Read models are disposable and rebuildable — just replay all events into a fresh projection
  • Multiple projections can serve different consumers’ needs without affecting the write side

Kafka-Based Pattern

In high-scale systems, Apache Kafka typically serves as the event bus:

Write Service ──► Event Store ──► Kafka Topic ──► Projection Service ──► Read DB
                                              └──► Analytics Service ──► Analytics DB
                                              └──► Search Service ──► Elasticsearch

Kafka’s durable, replayable log means:

  • New projections can be bootstrapped by replaying historical events
  • Multiple consumers each maintain their own read model without coordination
  • Single Views (denormalized documents representing a complete entity) are a common pattern

Benefits of Combining

BenefitHow ES+CQRS Achieves It
Full audit trailEvery state change is an event in the store
Optimized readsEach projection tailored to its consumers
Change trackingEvent history shows who changed what, when
Audit complianceImmutable event log satisfies regulatory requirements
Temporal queriesReplay up to a timestamp to see historical state
Read model rebuildDrop and recreate projections by replaying events
Reduced contentionWrite side and read side scale independently

Schema Evolution

Event schemas evolve over time. Use Event Upcasting to transform old events to new schema at read time, or version events explicitly.

When to Use

Use when you need:

  • Regulatory audit requirements
  • Complex reporting with multiple read models
  • High read/write asymmetry
  • Temporal queries (historical state)
  • Multiple consumers needing different data shapes

Avoid when:

  • Simple CRUD with no audit requirements
  • Small team / limited operational capacity
  • No meaningful read/write asymmetry

Trade-offs

BenefitCost
Full audit trailSignificant architectural complexity
Rebuildable read modelsEventual consistency on read side
Independent read/write scalingEvent schema evolution management
Multiple tailored read modelsOperational overhead (event bus, projections)