Event Sourcing and CQRS

Learn when event sourcing and CQRS are worth their extra complexity in Clojure microservices, how write and read models diverge, and where teams usually overapply them.

Event sourcing: A pattern where the source of truth is a sequence of domain events rather than only the latest stored state.

CQRS: A pattern that separates the write model from the read model so commands and queries can evolve differently.

These patterns can be extremely valuable, but they also add real cost. They are strongest when the domain benefits from an event log, auditability, projections, or independently optimized read models. They are weaker when the team mainly wants trendy architecture language for a straightforward CRUD system.

Event Sourcing Changes the Source of Truth

In a typical CRUD system, the database row is the source of truth. In an event-sourced system, the durable event stream becomes the source of truth, and current state is reconstructed by applying those events in order.

That gives you:

  • auditability
  • replay
  • temporal reasoning
  • the ability to build new projections later

It also gives you:

  • event schema evolution concerns
  • projection maintenance
  • more operational moving parts

CQRS Separates Write and Read Concerns

CQRS is often paired with event sourcing, but it does not require it. The important idea is that the model used to validate and process writes may not be the same model used to answer reads efficiently.

That is useful when:

  • reads and writes have very different shapes
  • the system needs denormalized or aggregated views
  • read performance matters enough to justify dedicated projections

It is less useful when one straightforward model already serves both sides well.

The Main Design Payoff Is Explicit Flow

The real benefit is not ceremony. It is clarity:

  1. a command is validated
  2. events are produced
  3. write state changes are recorded
  4. projections update read models
  5. queries hit those read models

That pipeline can be powerful in domains with audit requirements, workflow history, or heavy read-model variation.

A Small Event Application Example

 1(defn apply-event [state event]
 2  (case (:event/type event)
 3    :account-opened (assoc state :account/id (:account/id event)
 4                                 :balance 0)
 5    :money-deposited (update state :balance + (:amount event))
 6    :money-withdrawn (update state :balance - (:amount event))
 7    state))
 8
 9(defn replay [events]
10  (reduce apply-event {} events))

That code is intentionally small. The key idea is that state is derived from events rather than updated in place without a durable history.

Snapshots and Projections Are Practical Tools

As event streams grow, replaying from the beginning for every load becomes impractical. That is where snapshots help. They shorten rebuild time by storing a derived state checkpoint.

Projections serve a different purpose: they build read models tailored to consumer needs. A projection is not the source of truth. It is a derived view optimized for a query shape or downstream use case.

Common Failure Modes

Using Event Sourcing for Simple CRUD by Default

If the system does not truly need event history or multiple derived views, the added machinery may not pay for itself.

Confusing Events with Internal Implementation Logs

Events should reflect meaningful domain facts, not every incidental code step.

Ignoring Evolution

Event schema changes, replay behavior, and projection versioning become major concerns once the system lives long enough.

Practical Heuristics

Choose event sourcing when history and replay are core to the domain. Choose CQRS when write behavior and read shape genuinely diverge. Use projections as derived views, not as hidden alternate sources of truth. If a simpler CRUD model already meets the business need, simplicity usually wins.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026