Written by

Halkwinds Editorial Team

Halkwinds Research & Editorial

Published April 11, 2026
Blog image
Engineering

Event Sourcing and CQRS: When These Patterns Are Worth the Complexity

A practical evaluation of event sourcing and CQRS — the genuine benefits, the operational overhead, and the systems they're well-suited for.

Event sourcing and CQRS have a reputation problem. They show up in conference talks and architecture diagrams as symbols of engineering sophistication, and they get adopted by teams who want to look serious about scale. But for every system that genuinely benefits from these patterns, there are three that adopted them prematurely and now maintain a distributed ledger of pain. As an engineering manager, your job isn't to chase architectural prestige — it's to decide whether the complexity these patterns introduce buys you enough to justify the cost. This article gives you a practical framework for making that call.

  • Background / Why This Matters
  • Core Concepts and Architecture
  • Implementation Strategy
  • Scaling and Operational Considerations
  • Common Mistakes / What to Avoid
  • Frequently Asked Questions
  • Conclusion

Background / Why This Matters

Most applications start with a straightforward pattern: a service receives a request, mutates a row in a relational database, and returns the result. This CRUD model is well understood, easy to hire for, and supported by decades of tooling. It works remarkably well for a large percentage of business software.

The trouble starts when the business asks questions the model can't answer. Why did this account balance change? Who approved this order and in what sequence? What did the system look like on the day of the audit three months ago? A CRUD database stores the current state and throws away the history. Once a row is updated, the previous value is gone unless you bolted on audit tables — which are notoriously incomplete and drift from the source of truth.

Event sourcing flips this. Instead of storing current state, you store the full sequence of events that produced it: AccountOpened, MoneyDeposited, MoneyWithdrawn. Current state becomes a projection you rebuild by replaying events. CQRS (Command Query Responsibility Segregation) is a related but separate idea: you split the write model (commands that change state) from the read model (queries that return data), allowing each to be optimized independently.

These patterns matter because certain domains — finance, healthcare, logistics, compliance-heavy systems — treat history and auditability as first-class requirements rather than afterthoughts. In those contexts, event sourcing isn't an optimization; it's a natural fit for how the business already thinks.

Actionable takeaway: Before evaluating these patterns technically, ask the business a single question — "Do we need to reconstruct why the system reached its current state?" If the answer is a shrug, you probably don't need event sourcing.

Core Concepts and Architecture

It helps to separate the two patterns clearly, because teams often conflate them and adopt both when they only needed one.

Event Sourcing

An event store is an append-only log. You never update or delete; you only append immutable events. The current state of any entity (an "aggregate" in domain-driven design terms) is derived by folding over its event stream. EventStore (EventStoreDB) is purpose-built for this and provides features like optimistic concurrency, stream subscriptions, and built-in projections. Teams also build event sourcing on top of Apache Kafka, though Kafka is a log/streaming platform rather than a dedicated event store, so you take on more responsibility for concurrency control and per-entity stream semantics.

CQRS

CQRS says your read and write paths don't have to share a model. Writes go through commands validated against the write model. Reads come from denormalized projections tuned for the queries your UI actually makes. This is enormously powerful when read and write workloads have wildly different shapes — for example, a single write that fans out into a dozen different dashboard views.

How They Combine

Event sourcing and CQRS are frequently paired because they complement each other: events written to the store become the input for building read projections. But you can use one without the other. You can do CQRS with two conventional databases and no event store. You can do event sourcing with a single read model. Treat them as independent decisions.

DimensionCRUDCQRS (no event sourcing)Event Sourcing + CQRS
History / auditManual audit tablesManual audit tablesNative, complete
Read optimizationLimitedHighHigh
Operational complexityLowMediumHigh
Consistency modelStrongOften eventual readsEventual reads
Team ramp-up timeDaysWeeksMonths
Debugging difficultyLowMediumHigh
Actionable takeaway: Decide on event sourcing and CQRS separately. Many teams get 80% of the benefit they wanted from CQRS alone, without the operational weight of an event store.

Implementation Strategy

If you've decided the patterns fit your domain, the implementation approach matters more than the tooling. Here's a sequence that reduces risk.

  1. Start with one bounded context. Do not rewrite your whole system. Pick a single subsystem where history genuinely matters — payments, order lifecycle, inventory movements — and prove the pattern there.
  2. Model events, not tables. Events should represent business facts in past tense (OrderShipped), not technical CRUD operations (OrderRowUpdated). If your events read like database triggers, you're doing event sourcing wrong.
  3. Design for schema evolution up front. Events are immutable and live forever. You will need to change their shape. Adopt versioning and an upcasting strategy on day one, before you have millions of events to migrate.
  4. Keep the write model small. Aggregates should be tightly scoped so event streams don't grow unbounded. Long-lived aggregates with tens of thousands of events force you into snapshotting to keep rebuild times reasonable.
  5. Build projections as rebuildable, not precious. The value of event sourcing is that read models are derived. You should be able to drop a projection and rebuild it from the event log at any time. Treat projections as caches, not sources of truth.

On tooling: EventStoreDB is a strong default when the event store is the heart of the system, because concurrency and subscriptions come out of the box. Apache Kafka shines when events already flow between many services and you want a shared streaming backbone — but budget for extra work around per-entity ordering and idempotency. This is exactly the kind of architectural decision where our engineering teams at Halkwinds help clients avoid committing to the wrong foundation before the first line of production code is written.

Actionable takeaway: Prove the pattern in one bounded context and measure the operational cost honestly before expanding. A successful pilot in a low-risk subsystem is worth more than any architecture whitepaper.

Scaling and Operational Considerations

This is where the honeymoon ends. The patterns scale technically, but they impose ongoing operational demands that CRUD systems don't.

Eventual Consistency Is a Product Decision

Because read models update asynchronously after writes, a user may submit a change and not see it reflected immediately. This "read-your-own-writes" problem must be handled deliberately — through UI patterns (optimistic updates), read-after-write routing, or waiting for projection acknowledgment. If your product managers assume strong consistency, surface this early, because it changes UX behavior.

Projection Rebuild Time

As event volumes grow, rebuilding a projection from scratch can take hours. Research and practitioner reports suggest that rebuild time becomes a real operational constraint once streams reach the millions-of-events range, though the exact threshold varies heavily with event size and projection logic. Mitigations include snapshotting aggregate state, parallelizing projection builds, and maintaining warm standby projections during migrations.

Storage Growth

An append-only store never deletes. Storage grows monotonically. This is usually cheap relative to the value, but you need retention, archival, and possibly stream compaction strategies documented before regulators or your cloud bill force the conversation.

Observability

Debugging a distributed, eventually consistent, event-driven system is materially harder than tailing a request log. Invest early in correlation IDs across commands and events, monitoring for projection lag, and dead-letter handling for events that fail processing.

Actionable takeaway: Add projection lag and rebuild time to your production dashboards from the start. These are the metrics that tell you the system is healthy — and the ones teams forget until an incident.

Common Mistakes / What to Avoid

  • Adopting the pattern for prestige. The most common failure mode is choosing event sourcing because it's fashionable, not because the domain demands it. If your team can't articulate a concrete audit, temporal, or replay requirement, stop.
  • Modeling events as CRUD in disguise. Events named EntityCreated, EntityUpdated, EntityDeleted defeat the purpose. You've taken on all the complexity and captured none of the business meaning.
  • Ignoring schema evolution until it hurts. Teams that don't version events early face painful migrations later, because you cannot simply run an ALTER TABLE on an immutable log.
  • Applying it everywhere. Not every part of your system needs event sourcing. A user-preferences service or a CMS is fine as CRUD. Reserve the pattern for the bounded contexts that earn it.
  • Treating projections as authoritative. If you start writing to read models directly, you've broken the model and lost the ability to rebuild safely.
  • Underestimating team ramp-up. Estimates vary, but experienced practitioners often report months, not weeks, before a team is fully productive with these patterns. Factor that into delivery timelines.
Actionable takeaway: Write a one-page justification before adopting event sourcing in any bounded context. If you can't fill the page with concrete requirements, the pattern isn't earning its place.

Frequently Asked Questions

Can we use CQRS without event sourcing?

Yes, and you often should. CQRS is simply separating read and write models. You can implement it with two conventional databases synchronized by a change feed or message queue, with no event store at all. Many teams get the read-scaling and read-optimization benefits they wanted from CQRS alone, while avoiding the operational overhead of a full event-sourced write model.

How do we handle a bug in past event processing logic?

This is one of event sourcing's genuine strengths. Because events are the immutable source of truth and projections are