skip to content

How does the object-oriented Command pattern relate to a command bus, CQRS, and event sourcing — and what is the difference between a command and an event?

level: principalimportance: nice to knowfreq 32%

answer

  1. same reification: object → process → system
  2. bus = invoker + middleware pipeline
  3. CQRS: write model vs read model
  4. event sourcing: store the log, fold to state
  5. command imperative & refusable; event past-tense & final

basics

~20 s

They are the same idea at different scales: a request reified as an object. A command bus routes command objects to handlers; CQRS separates command handling from reads. A command is an imperative request that may be rejected; an event is an immutable fact that already happened.

solid answer

~50 s

The Command pattern reifies a request so it can be routed, deferred, logged, and reversed. Scale that up and the invoker becomes a **command bus**: commands are plain serializable data with a type, and a dispatcher resolves the handler by type — which is where cross-cutting concerns (validation, authorization, transactions, retries, metrics) live as middleware. **CQRS** applies the split at architecture level: writes go through commands/handlers against a consistency-enforcing model, reads go through separate query models, so the two can be shaped and scaled independently. **Event sourcing** changes what is persisted: instead of storing current state, you store the ordered stream of events a handler produced, and rebuild state by replaying — the same 'replay the log' idea as an undo history, at durable scale. The crucial distinction: a **command** is imperative, addressed to one handler, and may be **rejected**; an **event** is past-tense, immutable, has many or zero subscribers, and cannot be refused — only compensated. Naming (`ShipOrder` vs `OrderShipped`) should make this obvious.

go deeper

for a junior

Know that a command is a request that may be rejected and an event is a fact that already happened, and that both are requests/notifications turned into objects.

for a middle

Explain the command-bus shape (command as data, handler resolved by type, middleware) and the basic CQRS write/read split, plus the naming convention that keeps commands and events distinct.

for a senior

Cover eventual consistency and its UX consequences, one-transaction-per-command, idempotency at the bus, and why event sourcing implies permanent event versioning and projection rebuilds.

for a principal

Argue the adoption decision explicitly: which of bus / CQRS / event sourcing is justified by which driver, what fixed costs each imposes (indirection, consistency model, schema-forever, operational surface), and how to keep command/event semantics honest across teams so responsibility for action never becomes ambiguous.

## One idea at three scales | Scale | Reified request | "Invoker" | Extras | |---|---|---|---| | Object (GoF Command) | `InsertTextCommand` object | button, history, thread pool | undo(), macro composition | | Process (command bus / CQRS) | serializable command message + handler | dispatcher with middleware | validation, authz, transaction, retry | | System (event sourcing / log) | persisted command or event record | log/queue + consumers | replay, audit, projections, temporal queries | Each level relies on the same move: **turn the request into data so it can be routed, stored, replayed, and inspected**. Nothing structurally new appears at the higher levels; what appears are the *consequences* of durability and distribution (schema versioning, at-least-once delivery, ordering, eventual consistency). ## Command bus When a request must be serializable and cross a boundary, you split the command (data) from the handler (behavior + dependencies): ``` command: PlaceOrder { orderId, customerId, lines[], idempotencyKey } handler: handle(PlaceOrder) → uses repositories, emits events bus: dispatch(command) → find handler by type → run middleware chain → handle ``` The dispatcher is the invoker generalized. Its real value is the **middleware pipeline**: one place to apply logging with the command type as a dimension, authorization by command type, input validation, transaction boundaries (one transaction per command is a clean default), retry/idempotency, rate limiting, and tracing. Compare with `execute()`-on-the-command: cross-cutting concerns then have to be woven in by decorating each command, which is exactly the Decorator-of-Command trick used at small scale. Trade-offs: dispatch becomes indirect (a dynamic registry rather than a compiler-checked call), stack traces get deeper, and "find the handler" needs tooling. Worth it when you need the pipeline, serialization, or out-of-process handling; not worth it for a service with a handful of internal calls. ## CQRS Command Query Responsibility Segregation: use one model to change state and a *different* model to read it. The write side is command-shaped — commands, handlers, invariants, one aggregate/consistency boundary per command. The read side is query-shaped — denormalized projections tuned to the screens that use them. Why: reads and writes have genuinely different requirements (read volume vs write invariants, denormalized shapes vs normalized integrity, different scaling profiles). Costs: two models to maintain, and if the read model is updated asynchronously, **eventual consistency** becomes user-visible (a user posts and does not immediately see it), which needs explicit UX handling — read-your-own-writes routing, optimistic UI, or version tokens. Note what CQRS does *not* require: it does not require event sourcing, separate databases, or async projections. Those are options that often accompany it. ## Event sourcing Instead of persisting current state, persist the ordered stream of events. Current state = fold over the stream. Typically the flow is: command → handler validates against the current (rebuilt or cached) state → emits one or more events → events are appended → projections update read models. Benefits: a complete audit trail by construction, temporal queries ("what did this look like on the 3rd?"), the ability to build new read models retroactively by replaying, and debugging by replay. Costs: schema/event versioning forever (you can never stop being able to read old events), snapshotting for long streams, projection rebuild time and operational complexity, and the difficulty of "deleting" data from an append-only log (relevant for privacy/erasure requirements — usually solved with crypto-shredding). The conceptual link to the GoF pattern is direct: an undo history *is* a small, volatile command log, and rebuilding state by replay is the same operation as redo from the beginning. ## Command vs event — the distinction that matters | | Command | Event | |---|---|---| | Grammar | imperative: `ShipOrder` | past tense: `OrderShipped` | | Meaning | a *request* to change state | a *fact* that already happened | | Recipients | exactly one handler | zero to many subscribers | | Can be refused? | yes — validation/authorization may reject it | no — it is already true; only compensation is possible | | Coupling | sender expects something to happen | publisher does not know or care who listens | | Failure meaning | the change did not occur | the change occurred; a consumer failed to react | Why it matters architecturally: turning a command into an event ('fire and forget, someone will handle it') hides the fact that a required action may silently never happen. Turning an event into a command ('the publisher tells the consumer what to do') re-couples the publisher to downstream logic and kills the extensibility that events exist to provide. Getting this backwards is one of the most common failures in event-driven design, and naming discipline (imperative vs past tense) is the cheapest guard. ## When to say no A principal-level answer includes the restraint: command bus + CQRS + event sourcing is a big fixed cost — indirection, eventual consistency, versioning forever, more operational surface. Adopt them where the driver is real: complex invariants on the write side, an audit/temporal requirement, wildly asymmetric read/write scaling, or many independent consumers of state changes. For a CRUD service with modest load, the plain Command pattern (or just a service method) is the correct amount of structure. Also, the boundaries can be adopted independently: command objects without a bus, a bus without CQRS, CQRS without event sourcing.

  • Does CQRS require event sourcing?
    No. CQRS only says the write model and read model are separate. You can implement it with a single relational database, synchronous updates, and no events at all. Event sourcing is a separate decision about how state is persisted; the two are frequently combined because an event stream is a convenient way to feed read projections.
  • How do you decide whether something should be a command or an event?
    Ask whether the sender requires a specific outcome. If yes — one handler must do this, and it may be rejected — it is a command, named imperatively. If the sender is only announcing a fact that has already become true and does not care who reacts, it is an event, named in the past tense. If you find a 'command' with several handlers, or an 'event' the publisher expects a particular consumer to act on, the model is wrong.
  • What breaks first when you adopt event sourcing casually?
    Event versioning and projection maintenance. Old events must remain readable forever, so every schema change needs an upcasting path; and long streams need snapshots or replays become slow. Deleting personal data from an append-only store is also genuinely hard, typically handled by encrypting per-subject and discarding the key.
  • Where do cross-cutting concerns live once dispatch goes through a bus?
    In the middleware pipeline around handler invocation — authorization by command type, validation, transaction boundary, idempotency/dedup, retry, metrics and tracing. Centralizing them there is the main practical reason to introduce a bus at all.

A command is a purchase order sent to one supplier, who may decline it. An event is the shipping notice: it states something that already happened, anyone may subscribe to it, and no one can decline it — the only remedy is a return.

saying these in an interview costs you the question

  • Claiming CQRS requires event sourcing, or that it requires two databases.
  • Using past-tense names for commands or imperative names for events, blurring who is responsible for acting.
  • Publishing an event when a specific action is required, so a mandatory step can silently never happen.
  • Sending a command where an event belongs, coupling the publisher to downstream consumers and blocking new subscribers.
  • Assuming event sourcing gives you scalability; its real payoffs are auditability, temporal queries, and retroactive projections.
  • Treating an event as rejectable — an event is already a fact; only compensation is available.
  • Introducing a command bus, CQRS and event sourcing together on a simple CRUD system and calling the resulting indirection 'clean architecture'.

context