Day 092: Event Sourcing and CQRS
Event sourcing and CQRS are useful when the business cares not only about what the current state is, but also about how that state came to be, and when the model that protects invariants is not the same model people need for reading, reporting, or querying.
Today's "Aha!" Moment
These two patterns are often taught together and then misunderstood together. People hear "event sourcing" and think "we also save an audit trail." They hear "CQRS" and think "we split reads and writes into different databases because that sounds scalable." Neither description is quite right.
Keep one example throughout the lesson. The learning platform runs live cohorts with limited seats, withdrawal deadlines, waitlists, and enrollment history that matters to support, reporting, and compliance. A simple table with available_seats = 3 can tell you the current state, but it cannot explain how the cohort got there, what sequence of enrollments and withdrawals occurred, or how to rebuild different read models after a bug.
That is the aha. In event sourcing, the authoritative truth for a domain boundary is not just the current row. It is the sequence of domain events that produced the current state. In CQRS, the write side is optimized for enforcing business rules, while the read side is allowed to have different shapes built for queries and views.
Once you see those two ideas separately, the pairing makes more sense. Event sourcing says, "history is first-class truth here." CQRS says, "the write model that protects correctness does not have to be the same model we expose for reads." They often fit together, but neither should be applied mechanically.
Why This Matters
The problem: Current-state storage is often enough for simple CRUD, but it becomes awkward when history matters to the business or when query needs put pressure on a write model designed around invariants rather than reporting.
Before:
- The system stores only the latest state and loses the business narrative of how it arrived there.
- Audit, reconstruction, and backfill workflows are harder than they need to be.
- Read-heavy views depend directly on write-side structures that were not designed for those queries.
After:
- Domain history can become explicit and authoritative where that matters.
- Read models can be rebuilt or reshaped from the event history.
- The write side stays focused on correctness, while read projections stay focused on access patterns and query convenience.
Real-world impact: Better auditability, more resilient projections, cleaner reporting models, and more flexible recovery options, but only in domains where that extra power is worth the extra operational and conceptual cost.
Learning Objectives
By the end of this session, you will be able to:
- Explain what event sourcing changes about truth and state - Distinguish history-as-source-of-truth from current-row-as-source-of-truth.
- Explain what CQRS actually separates - Understand why write-side rule enforcement and read-side query shapes do not always belong in one model.
- Judge when these patterns fit - Recognize the difference between a domain that benefits from them and one that would just inherit complexity.
Core Concepts Explained
Concept 1: Event Sourcing Makes the History of Domain Decisions Authoritative
In event sourcing, the system does not merely log what happened after updating state elsewhere. The ordered domain events are the primary record from which current state can be reconstructed.
For the cohort example, the important facts might be:
cohort.createdstudent.enrolledstudent.withdrawnwaitlist.promotedenrollment.closed
Current state is then derived from that sequence rather than treated as the only truth.
cohort.created
student.enrolled (alice)
student.enrolled (bob)
student.withdrawn (alice)
waitlist.promoted (carla)
--------------------------------
derived current state:
enrolled = [bob, carla]
seats_remaining = 0
This is why event sourcing is not the same as audit logging. An audit log usually observes a system whose truth lives somewhere else. In event sourcing, replaying the event stream is how the system reconstructs the aggregate state itself.
The trade-off is richer historical truth versus more design responsibility. You gain temporal reasoning, replay, and precise reconstruction, but you must now care deeply about event design, versioning, and replay semantics.
Concept 2: CQRS Separates the Model That Protects Correctness from the Models That Serve Reads
The write side of the cohort system is concerned with invariants:
- do not exceed seat count
- do not enroll after the deadline
- do not promote from the waitlist incorrectly
That model is often compact, rule-heavy, and optimized around making valid decisions. But support dashboards, instructor summaries, and learner timelines usually want different shapes entirely.
That is where CQRS helps. It says the authoritative command side and the query side do not need to share one model just because they refer to the same business area.
write side:
command -> validate rules -> emit event
read side:
consume events -> build query projection
def apply(event, state):
if event["type"] == "student.enrolled":
state["enrolled_count"] += 1
elif event["type"] == "student.withdrawn":
state["enrolled_count"] -= 1
return state
The code only illustrates the idea that state can be derived from ordered events, while read models may build totally different projections such as "cohorts by instructor," "recent withdrawals," or "seat changes by week."
The trade-off is specialized read models versus more moving parts. You gain query flexibility and often much cleaner read performance, but you also accept that projections may lag and that more than one representation of the same business area now exists.
Concept 3: These Patterns Add Real Power Only If the Domain Actually Needs History and Projections
This is where teams most often go wrong. Event sourcing and CQRS are not general upgrades to ordinary CRUD. They are heavier tools for domains where history, reconstruction, temporal questions, or multiple important projections are genuinely central.
For the cohort system, these questions may matter a lot:
- why did the seat count change over time?
- who was promoted from the waitlist and when?
- can we rebuild support views after a projection bug?
- can we explain a learner dispute using durable domain history?
If those questions are business-relevant, the patterns may fit. If the domain is simply "store a profile, edit a profile, read a profile," they may not.
good fit:
rich history matters
replay has value
read views diverge from write rules
bad fit:
current state is enough
history is incidental
one simple model already serves both reads and writes
The trade-off is capability versus complexity. These patterns can be excellent where the domain rewards them, but expensive and distracting where ordinary state storage already solves the real problem.
Troubleshooting
Issue: Treating event sourcing as just "logging more data."
Why it happens / is confusing: Both event stores and audit logs preserve history, so they sound similar at first.
Clarification / Fix: Ask where truth lives. If current state is still authoritative and the history is only observational, that is not event sourcing.
Issue: Using CQRS as a scalability slogan.
Why it happens / is confusing: Read/write separation is often presented as a performance trick instead of a modeling decision.
Clarification / Fix: Start from semantics and query shape, not from infrastructure. CQRS is useful when the read model and the write model genuinely want to be different.
Issue: Ignoring replay, projection lag, and event evolution costs.
Why it happens / is confusing: The patterns are often introduced through their benefits and not through the operational discipline they require.
Clarification / Fix: Treat event schema evolution, projector rebuilds, idempotency, and lag visibility as core design concerns, not implementation details.
Advanced Connections
Connection 1: Event Sourcing and CQRS ↔ Streaming
The parallel: Event-sourced systems often feed projections in a way that looks similar to streaming, but the event store and the read projections do not serve the same architectural role as Kafka topics and generic consumers.
Real-world case: A cohort aggregate may be event-sourced for authoritative history while several query views consume the resulting events to build dashboards and support tools.
Connection 2: Event Sourcing and CQRS ↔ Distributed Workflow Design
The parallel: Once a workflow emits durable domain events, those events can support both correctness inside the aggregate boundary and loosely coupled read or reaction models outside it.
Real-world case: Enrollment workflows may use explicit domain events for state transitions while support, reporting, and analytics consume read-optimized projections built from that history.
Resources
Optional Deepening Resources
- These resources are optional and are not required for the core 30-minute path.
- [DOC] Event Sourcing Pattern
- Link: https://microservices.io/patterns/data/event-sourcing.html
- Focus: Review the core motivation, benefits, and trade-offs of treating events as the source of truth.
- [PDF] CQRS Documents
- Link: https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf
- Focus: Read the original framing of CQRS as a modeling choice, not merely a scaling tactic.
- [BOOK] Implementing Domain-Driven Design
- Link: https://vaughnvernon.co/?page_id=168
- Focus: Connect aggregates, domain events, and event-sourced design in rich domains.
- [BOOK] Designing Event-Driven Systems
- Link: https://www.confluent.io/designing-event-driven-systems/
- Focus: Compare authoritative event histories with broader streaming and projection patterns.
Key Insights
- Event sourcing makes domain history authoritative - The current state is derived from ordered events rather than treated as the only truth.
- CQRS separates rule enforcement from query convenience - The write model and read models can differ because they have different jobs.
- Both patterns are power tools, not defaults - They pay off when history, replay, and specialized projections really matter to the domain.
Knowledge Check (Test Questions)
-
What makes event sourcing different from ordinary logging?
- A) The event history is the authoritative record from which current state is reconstructed.
- B) The system never stores current state in memory.
- C) Every log line automatically becomes a query model.
-
What problem is CQRS mainly addressing?
- A) The write model that protects business invariants and the read model that serves queries may need different shapes.
- B) Every system should split reads and writes into separate databases by default.
- C) Read models should always be strongly consistent with writes in the same transaction.
-
When are event sourcing and CQRS most likely to be justified?
- A) When history, replay, auditability, and specialized projections are genuinely important to the domain.
- B) Whenever a team wants to use events somewhere in the architecture.
- C) Whenever a CRUD system starts to feel ordinary.
Answers
1. A: In event sourcing, the ordered domain events are the primary truth, not just an observational trail beside some other authoritative store.
2. A: CQRS is useful when correctness and query convenience pull the model in different directions.
3. A: These patterns are strongest where the domain truly benefits from durable history and rebuilt or specialized read models.