skip to content

questions

6

In an architecture that combines Event Sourcing with CQRS (Command Query Responsibility Segregation), a client submits a command to change data. Walk through what happens on the write side before that change becomes visible to a query.

level: juniorimportance: must knowfreq 80%

answer

  1. load-replay-validate-append
  2. aggregate state is derived from replay, not stored
  3. command side never writes the read model directly
  4. optimistic concurrency via expected stream version
  5. publish then project asynchronously

basics

~10 s

The command side loads the past events for that thing, checks the business rules, creates one new event describing what happened, and appends it. Nothing is overwritten, only added.

solid answer

~30 s

A command handler loads the target aggregate by replaying (or restoring from a snapshot plus replaying) its historical events to get current state, validates the command against business rules, and if valid, appends exactly one new event to that aggregate's stream using an optimistic-concurrency check against the expected stream version. The event store is the append target, not the read database. Once appended, the event is published so downstream projectors can update read-model tables asynchronously. The command handler never writes to the query-side store directly, which is why the two sides can be scaled, modeled, and even stored independently.

go deeper

for a junior

Can describe the load-validate-append flow at a high level, ideally with an analogy like a ledger.

for a middle

Can name optimistic concurrency and stream versioning, and explain why snapshots help replay performance.

for a senior

Can discuss the transactional boundary (one aggregate equals one append), idempotent event publishing, and the trade-offs against plain CRUD.

for a principal

Can argue when Event Sourcing and CQRS are worth adopting at all versus simpler approaches, and the organizational cost of owning event schemas and projections long-term.

## What each half owns **Event Sourcing** stores state as an append-only sequence of immutable domain events rather than as mutable rows; the current state of an entity (an 'aggregate' in domain-driven design terms) is derived, not stored directly, by replaying its events in order. **CQRS** separates the model used to change state (commands) from the model used to read state (queries). Combined, the command side owns an aggregate and its event stream, while the query side owns one or more read-model projections built from those events. ## The flow, step by step Concretely, the flow is: - **(1)** a command arrives at a command handler naming a target aggregate by id; - **(2)** the handler loads that aggregate by fetching its event stream from the event store and replaying every event through the aggregate's apply logic to rebuild current in-memory state (for long streams this is sped up with a periodic snapshot, so replay only covers events since the last snapshot); - **(3)** the handler runs domain validation against that reconstructed state -- for example, refusing to ship an order that was already cancelled; - **(4)** if valid, it produces one new event (e.g. `OrderShipped`) and appends it to the stream, passing the version number it expected the stream to be at; - **(5)** the store either accepts the append, giving the stream a new version, or rejects it if another command changed the aggregate first (optimistic concurrency), forcing the handler to retry against fresh state; - **(6)** once appended, the event is durable and becomes the new source of truth; - **(7)** separately and asynchronously, one or more projectors subscribed to the event stream or a downstream bus consume the new event and update whatever read-model tables or search indexes serve queries. ## Why it exists This exists because a single mutable row model forces every reader and writer through one shape and one store, coupling scaling, schema, and technology choices that often have very different needs: writes need strong consistency and small, fast transactions per aggregate, while reads often need denormalized, search-optimized, or aggregated shapes serving far higher volume. Event Sourcing additionally gives a **full audit trail for free** (every state change is a permanent, inspectable fact) and lets you rebuild any number of new read models later by replaying history, since nothing was ever discarded on write. ## The trade-offs The trade-offs are real. On the plus side: - strong auditability; - the ability to add new projections after the fact by replaying history; - and a write path that is fast and uncontended because it only ever appends to one stream per command. On the minus side: - the read side is only eventually consistent with the write side (a projector takes some non-zero time to catch up); - the team must design and maintain event schemas that will live forever (since old events can never be silently rewritten); - and debugging requires reasoning about a system that has more moving parts than a single CRUD database. Complexity is the toll paid for those benefits, and teams that adopt Event Sourcing for entities with no auditing or historical-replay need often find the toll isn't worth it. ## Failure modes Failure modes show up mainly around concurrency and durability. - **Two commands race against the same aggregate.** The second one to attempt its append fails the optimistic-concurrency check and must retry -- if a handler doesn't implement retry, users see spurious command failures under load. - **The event store or the publish step fails** between appending and notifying projectors. The write is still durable (it's in the store) but projectors may lag until they catch up on their next poll or subscription resume; this is normal, not data loss, as long as projectors are built to resume from a durable checkpoint rather than relying on a live-only push. - **A snapshot mechanism is buggy.** Aggregate reconstruction can be slow (no snapshot, full replay every time) or, worse, wrong (a stale or corrupted snapshot silently producing incorrect state). ## Where it shows up A concrete, widely used real-world shape of this pattern is a banking ledger or an order-management system built with a framework such as **Axon Framework** or backed by a store such as **EventStoreDB**, where account balance or order status is never stored as a column to be updated in place; instead, every deposit, withdrawal, or status change is appended as its own event, and the balance or status shown to a user is a read-model column maintained by a projector that has consumed that account's or order's full event history.

  • What stops two concurrent commands against the same aggregate from producing conflicting events?
    The append call passes the version the handler expected the stream to be at. If another command already appended an event and moved the stream to a newer version, the store rejects the second append as a conflict. The handler then reloads the current state and either retries the command or surfaces a conflict to the caller.
  • Why not just update the read model synchronously inside the same operation as the event append?
    Doing so would couple the write path's latency and availability to every read model's store, defeating the point of separating them, and it often isn't even possible when the read model lives in a different storage technology such as a search index. Systems that truly need read-your-writes for a specific flow usually solve it by returning the updated data directly in the command's response, or reading straight from the just-updated aggregate, rather than making every projection synchronous.

Like a bank passbook: instead of erasing and rewriting your balance, each transaction is written as a new line in the book, and your current balance is just the sum of every line so far.

saying these in an interview costs you the question

  • describes commands as directly updating the read model or database row
  • thinks the aggregate's state is stored rather than derived by replaying events
  • no mention of any concurrency or version check on append
  • conflates the event store with a plain queue that has no durable, replayable log
  • assumes read-side updates happen synchronously with the write

context

open as a page

In a system where the write side is event-sourced and a separate read side serves queries from projections built off those events, why is the read side typically eventually consistent, and what problem does that cause for a user who just submitted a change and immediately re-queries?

level: middleimportance: must knowfreq 85%

basics

~20 s

Events travel from the write side to the read side afterward, taking a little time. If you read right after writing, the read model may not have caught up yet, so you can briefly see stale data.

open as a page

A read model in an event-sourced CQRS system is built by a background projector that subscribes to the event stream and updates a query-optimized table. In production, list the concrete ways this projection pipeline fails, and how you'd detect and recover from each.

level: seniorimportance: must knowfreq 65%

basics

~20 s

The projector can crash, fall behind, receive duplicates, or choke on a bad event. Fix these with durable checkpoints, lag monitoring, idempotent updates, and the ability to replay events to rebuild the table from scratch.

open as a page

In an event-sourced CQRS system, a single business workflow such as placing an order needs to update several independent aggregates -- say Order, Inventory, and Payment -- each with its own consistency boundary. What role does a process manager (saga) play here, and how does it drive the workflow forward?

level: middleimportance: should knowfreq 70%

basics

~20 s

A saga listens for events, and each time something happens it decides the next command to send to the next aggregate. It's like a conductor reacting step by step, instead of one big all-or-nothing transaction across everything.

open as a page

You're designing the aggregates for an event-sourced order-management system with CQRS. How do you decide where to draw each aggregate's consistency boundary, and what's the trade-off between making an aggregate bigger, to get more invariants enforced for free inside one transaction, versus splitting it and coordinating across the split via a process manager?

level: principalimportance: should knowfreq 45%

basics

~20 s

An aggregate should be just big enough to enforce the rules that must be true at every single instant. Anything that can tolerate a short delay to become consistent should live in a separate aggregate, coordinated afterward by a saga.

open as a page

An event type OrderPlaced has been in production for two years with a fixed set of fields. The business now needs to add a required 'currency' field to how orders are represented, but the event store's old OrderPlaced events don't have it. How do you evolve the event schema without breaking existing projections, and what does this have to do with rebuilding read models?

level: seniorimportance: nice to knowfreq 35%

basics

~20 s

You never rewrite old events; they stay exactly as they were recorded. Instead you translate old events into the new shape on the fly when they're read, or version the event type, and any projector reading them handles both old and new shapes.

open as a page