Pattern

How I Implement CQRS: Three Streams, One Event Store, One Projection System

CQRS is usually described as a clean split between "reads and writes." In reality, that description is far too shallow to build real systems. This pattern describes a model that treats CQRS as three distinct streams, each optimized for a different mode of interaction.

CQRS is usually described as a clean split between "reads and writes."

In reality, that description is far too shallow to build real systems.

Over time, I've converged on a model that treats CQRS as three distinct streams, each optimized for a different mode of interaction:

  1. The Command Stream
  2. The Query-by-ID Stream
  3. The Search & Filter Stream

The first two streams run directly on the event store and represent the authoritative truth. The third stream is powered by projections optimized for exploration and filtering.

This structure keeps systems clean, scalable, and predictable.

But there is one critical architectural case where this separation is not enough: interactive dashboards that require synchronous projection updates.

It's a case that most CQRS discussions ignore — but it matters deeply for real user experience.

CQRS architecture diagram showing three streams: Command Stream, Query-by-ID Stream, and Search & Filter Stream, with Event Store as source of truth, Projection Engine, and both synchronous and asynchronous projection update paths

1. The Command Stream — Changing the World

The Command Stream is the only entry point for modifying system state.

A command expresses intent:

  • "approve this"
  • "change this"
  • "transition this entity to another state"

The command handler:

  • loads the aggregate from events
  • validates invariants
  • applies domain rules
  • emits new events
  • commits them to the event store

This stream enforces correctness and domain behavior. Nothing else is allowed to mutate the system.

2. The Query-by-ID Stream — Reading Authoritative Truth

The second stream answers a simple, essential question:

"What is the true, current state of this entity?"

To answer it, the system replays all events for that entity and reconstructs its canonical state.

This stream offers:

  • strong consistency
  • correctness
  • full history
  • predictable semantics

It is not designed for search or filtering. It is designed for truth.

3. The Search & Filter Stream — Optimized for Exploration

Exploration is where users spend most of their time: searching, filtering, sorting, drilling into lists, and navigating dashboards.

These operations are served by projections, not the event store.

Projections:

  • denormalize event data
  • optimize it for lookup
  • support flexible filtering and sorting
  • update asynchronously

They are consciously eventually consistent, which is fine for list views and ordinary exploration.

But dashboards introduce a completely different requirement.

The Critical Exception: Synchronous Projection Updates for Dashboards

This is the most important architectural nuance of the entire model.

Most projections update asynchronously — and that's correct for exploration. But dashboards are interactive, user-driven reflections of the system's state.

When a user changes something and immediately views a dashboard, they expect to see the updated numbers instantly. If the dashboard still shows stale data because the projection has not yet been updated, the system feels broken, even if it is technically working as designed.

Users don't think in terms of "eventual consistency."

They think in terms of "I just changed something — show it to me."

This requires a different path: a synchronous projection update, triggered directly from the Command Stream.

How the synchronous path works

  1. The frontend knows exactly which projection powers the dashboard.
  2. When it issues a command, it attaches the projection name.
  3. The command handler processes the command normally and persists the events.
  4. Immediately after the events are committed, the command handler calls the projection logic synchronously for:
    • the affected entity
    • the specific projection requested
    • the requesting user's context
  5. The updated projection result is returned instantly to the frontend.

Why this is important

  • The event store remains the source of truth — no shortcuts.
  • The projection system remains asynchronous for everyone else.
  • Only the minimum-required projection slice is updated synchronously.
  • The user experience remains coherent: they see exactly what they expect.

This is not a hack. It is a deliberate architectural refinement that preserves UX without undermining the core principles of event-driven design.

What this pattern achieves

  • The system stays event-sourced and consistent.
  • Dashboards behave like real-time instruments, not lagging indicators.
  • No projection bottlenecks form because only targeted updates run synchronously.
  • Users experience correctness and immediacy simultaneously.

Most CQRS discussions ignore this scenario, but in real collaborative systems, dashboards are central. They define the user's mental model of "what the system is right now."

Synchronous projection updates make that mental model accurate.

Why the Three-Stream Model + Synchronous Path Works

By explicitly recognizing these three streams — and the special synchronous projection path — the architecture becomes clearer:

  • Commands enforce business rules and correctness.
  • Query-by-ID ensures authoritative truth.
  • Search & Filter supports exploration and usability.
  • Synchronous projection updates align the user experience with the underlying model.

Each stream is optimized for the role it plays.

Instead of a vague "reads vs. writes" split, we get a precise and predictable structure:

intent → truth → exploration → instant projection when UX requires it

This is how I implement CQRS in real systems. It is a pragmatic, consistent approach that respects the domain, the event store, and — critically — the user's understanding of the system.

Related patterns

This pattern works well with:

Explore more patterns

This pattern is one of many for building collaborative systems. Check out other patterns or dive deeper into the framework components.