skip to content

In a system that separates commands from events, what is the key difference between a command like `PlaceOrder` and an event like `OrderPlaced`, and why does that naming difference matter for how each one is handled?

level: juniorimportance: must knowfreq 75%

answer

  1. verb tense = contract
  2. commands: one handler, can fail
  3. events: many subscribers, immutable fact
  4. imperative vs past-tense naming
  5. request vs record

basics

~20 s

A command asks the system to do something and can be refused, like a request. An event says something already happened and is a fact - you can't refuse a fact, only react to it.

solid answer

~40 s

Commands are imperative, addressed to exactly one recipient: PlaceOrder, CancelSubscription. They express intent about the future and can be rejected - bad input, a broken business rule, insufficient funds - because nothing has happened yet. Events are past-tense facts: OrderPlaced, SubscriptionCancelled. They record something that already succeeded and are immutable; you cannot 'reject' an event, only choose whether to act on it. Structurally this maps to different fan-out and failure semantics: a command has one handler that reports success or failure, while an event can have zero, one, or many subscribers that each react independently and can't veto what already happened. Getting the tense right in naming isn't cosmetic - it tells every future reader whether the message can still fail or is already committed history.

go deeper

for a junior

Should recognize the naming convention, imperative vs past-tense, and give one example of each without confusing them.

for a middle

Should explain that commands can be rejected while events can't, and connect that to why command handlers report success or failure.

for a senior

Should discuss fan-out differences, one handler vs many subscribers, and how that shapes error handling and coupling in a real system.

for a principal

Should discuss how this distinction shapes system-wide contracts - why teams version events differently from commands, and how misnaming a message type causes downstream coupling and replay bugs.

## Two vocabulary types on the write side In a message-driven write side, two vocabulary types coexist, and confusing them is one of the most common design mistakes teams make. - A **command** is imperative in name and intent: `PlaceOrder`, `TransferFunds`, `CancelSubscription`. It is addressed to exactly one recipient — a single handler owns the right to accept or reject it — and it represents something that has not happened yet. Because nothing has happened, a command can legitimately fail: the order might be invalid, the account might lack funds, the subscription might already be cancelled. - An **event**, by contrast, is past-tense: `OrderPlaced`, `FundsTransferred`, `SubscriptionCancelled`. It records something that has already happened and succeeded. Events are immutable historical facts, published to zero, one, or many subscribers who each react independently, and none of them gets a vote on whether the thing happened — it already did. ## Why the distinction exists This distinction exists because a system needs two different conversations happening at different points in time, with different failure semantics. - The **'can this happen'** conversation belongs to commands: it needs authorization, validation against current state, and a clear accept/reject outcome that the sender can act on. - The **'this happened, does anyone care'** conversation belongs to events: it needs broadcast, not permission; consumers subscribe voluntarily and process at their own pace, some immediately, some hours later, some never. Collapsing these into one message type produces systems where nobody can tell, just from looking at a message name, whether it's still safe to reject or already too late. ## The trade-off The trade-off shows up on both sides. | Message type | What it gains | What it gives up | |---|---|---| | **Commands** | buy synchronous certainty — the caller usually gets a definite yes/no back | at the cost of tight coupling: the sender must know the exact command shape and exactly one handler must exist to own it, and the sender typically has to build explicit failure-handling paths for every rejection reason | | **Events** | buy loose coupling and scalability — any number of services can subscribe to `OrderPlaced` without the order-placing code knowing or caring who's listening | at the cost of much harder reasoning: you can no longer look at the code and see who reacts to a given fact, ordering and delivery guarantees become distributed-systems problems, and once published there is no 'undo,' only a further compensating event | ## The classic failure mode: blending the two In production, the classic failure mode is blending the two. 1. A team publishes something named imperatively — say, `ProcessRefund` — onto a shared event-style topic where 'whichever service happens to be subscribed' picks it up, but nobody has designed for the case where two services both claim it, or none does, because event topics don't inherently guarantee single-consumer ownership the way a command dispatch table does. 2. The mirror-image mistake is naming something past-tense — `OrderCancelled` — but routing it as a targeted, single-consumer, must-succeed-synchronously call; consumers built expecting a fire-and-forget notification suddenly have to handle synchronous failure paths for a message whose name told them it was already a done deal. Both mistakes usually surface as confused on-call incidents: a 'command' silently drops because nobody was subscribed, or an 'event' blocks a critical path because its one consumer was down. ## A checkout flow, end to end A concrete example: in an e-commerce checkout flow, the API layer sends a `PlaceOrder` command to the order-management module and waits synchronously for the result, because the shopper needs an immediate 'order confirmed' or 'card declined' response before the page can move on — that's a command relationship, one sender, one handler, a definite answer. Once the order handler validates the request and commits the new order, it raises `OrderPlaced` onto a broker. Inventory service, notification service, and analytics service each subscribe to that event independently; none of them can reject the order at that point, they can only decrement stock, send a confirmation email, or log a metric in reaction to a fact that has already been recorded. If the notification service is down when `OrderPlaced` fires, the order still stands — the customer just gets their confirmation email late, which is a very different failure mode than if `PlaceOrder` itself had failed to reach its handler. ## Different message, different plumbing This also explains why the two message types are usually implemented with different infrastructure primitives even inside the same codebase. - **Commands** are often modeled as direct method calls or point-to-point queues with a single consumer group, because the whole point is that exactly one piece of code owns the decision, and routing a command to more than one handler by mistake, for instance a misconfigured queue with two competing consumers, produces a genuinely dangerous bug: the same `PlaceOrder` might get approved twice by two different handler instances racing each other. - **Events**, on the other hand, are naturally modeled as publish-subscribe topics, because the whole point is that the publisher doesn't know or care how many listeners exist, and adding a fourth subscriber to `OrderPlaced` next quarter should require zero changes to the order-placing code. Recognizing which infrastructure pattern a given message needs is a direct, practical consequence of getting the command-versus-event classification right at design time, not an afterthought bolted on once the messaging layer is already built.

  • Can a command handler publish multiple events?
    Yes - a single command like PlaceOrder might, on success, produce OrderPlaced plus other events if the aggregate's boundary covers more than one concern, or more commonly one event plus follow-up commands dispatched to other aggregates afterward. The handler decides the full set of state changes and events atomically within its own aggregate and transaction boundary.
  • What happens if a command has no handler registered?
    That's typically a hard configuration or routing error at dispatch time, not a business rejection - the caller gets an error like 'unknown command type' rather than a validation failure. This differs from events, where having zero subscribers is a normal, silent, and valid state.
  • Why can't you 'undo' an event the way you can reject a command?
    An event describes something already persisted and, in many designs, already had side effects - other services have reacted to it. Undoing it would mean lying about history; the correct move is to publish a new compensating event, such as OrderCancelled, rather than retract the old one.

A command is like handing a waiter your order - the kitchen can still refuse it ('we're out of salmon'). An event is like the receipt printed after the meal was cooked and served - it's a record of what happened, and nobody can un-print it or argue with it, they can only file it.

saying these in an interview costs you the question

  • Names commands in past tense or events in imperative, e.g. a queue called 'OrderPlace' used as an event
  • Says a command handler can 'fire and forget' with no way to report rejection
  • Treats events as something that can be rejected or fail validation the same way commands do
  • Can't explain why an event can have many subscribers but a command routes to exactly one handler

context