Loading learning content...
In distributed systems, we exchange messages between components. But not all messages are created equal. Two fundamentally different message types exist, each with distinct semantics, design implications, and appropriate use cases: Events and Commands.
The distinction may seem subtle—both are just messages, after all. But conflating them leads to architectural confusion, inappropriate coupling, and systems that are harder to reason about and maintain. Understanding this distinction is not pedantic—it's essential for building well-designed event-driven systems.
This page will give you an exhaustive understanding of events versus commands, when to use each, and how the choice shapes your entire system architecture.
By the end of this page, you will understand the semantic, grammatical, and design differences between events and commands, how each affects system coupling and ownership, common patterns for using both together, and how to make the right choice in ambiguous situations.
Let's establish precise definitions:
Event: An immutable record of something that has already happened. Events describe facts about the past. They are notifications, not requests. The producer is informing, not asking.
Command: A request or instruction for an action to be performed. Commands express intent for something to happen in the future. The producer is asking, not informing.
The Grammatical Test:
A simple test: events are named in past tense; commands are named in imperative mood.
| Type | Naming Pattern | Examples |
|---|---|---|
| Event | Past tense (what happened) | OrderPlaced, PaymentReceived, UserRegistered, ItemShipped |
| Command | Imperative (what to do) | PlaceOrder, ProcessPayment, RegisterUser, ShipItem |
The Semantic Difference:
Beyond grammar, the semantic difference is profound:
This distinction has cascading implications for system design, ownership, coupling, and failure handling.
Events are like history. You can read about World War II, but you can't reject it or ask for it to be undone. It happened. Commands are like requests to your team. You can ask someone to deploy a fix, but they might refuse, fail, or need more information. The request expressing your wish is fundamentally different from describing what was done.
One of the most important differences between events and commands is their ownership model and direction of coupling.
Why Ownership Matters:
In a large organization, ownership determines who changes what. If the Order Service publishes OrderPlaced events, the Order team owns that event's schema. If someone wants new data in the event, they must request it from the Order team.
Conversely, if the Payment Service accepts ProcessPayment commands, the Payment team owns that command's schema. Anyone wanting to process payments must conform to what the Payment Service accepts.
This ownership model keeps boundaries clean. Events flow out from domain owners. Commands flow in to capability providers.
With events, consumers couple to producers—they depend on the event schema the producer publishes. With commands, senders couple to receivers—they depend on the command format the receiver accepts. Understanding this coupling direction helps you reason about which teams need to coordinate during changes.
Events and commands have fundamentally different failure semantics, which affects how you design error handling, retries, and compensating actions.
| Scenario | Event Behavior | Command Behavior |
|---|---|---|
| Message published | Fact recorded—cannot be 'undone' | Request submitted—outcome uncertain |
| Consumer/handler unavailable | Event buffered; processed later | Command may timeout; sender must decide |
| Processing fails | Retry is safe (event is still true) | Retry may be unsafe (side effects already occurred?) |
| Validation fails | What does this mean? Event is fact—it happened | Command rejected; sender notified of failure |
| Business rules reject | Events aren't rejected—they describe reality | Command rejected; compensating action needed |
| Need to undo | Publish compensating event (e.g., OrderCancelled) | Rollback or compensating command |
The Rejection Problem:
Consider a subtle but important question: Can you reject an event?
In the strict sense, no. The event describes something that already happened. If you receive PaymentReceived, you cannot reject it—the payment was received. You might choose to take no action, but the event remains true.
With commands, rejection is expected. If you receive ProcessPayment and the account has insufficient funds, you reject it. The payment was not processed. The command failed, and the sender needs to know.
Compensating Events:
Because events cannot be undone (they're historical facts), we use compensating events to record subsequent state changes:
OrderPlaced followed by OrderCancelledPaymentReceived followed by PaymentRefundedUserRegistered followed by UserDeactivatedThe original event remains true—the order was placed. The compensating event records that it was later cancelled.
Because events describe facts, processing the same event twice should be safe—the fact hasn't changed. Consumers MUST be idempotent. Commands present a harder problem: did the previous attempt succeed or fail? Did it have side effects? Command handlers need careful idempotency design to avoid duplicating effects.
The temporal nature of events and commands creates important design implications:
Here's a useful thought experiment: If you replay all your messages from the beginning of time, what happens? With events, you should be able to reconstruct current state—events form a complete history. With commands, replay would cause chaos—you'd re-execute all requests, duplicating side effects. This is why event sourcing works but 'command sourcing' doesn't.
The choice between events and commands has profound implications for how tightly or loosely coupled your system becomes.
Events Enable Loose Coupling:
When you publish events, you don't know (or care) who consumes them. The Order Service announces OrderPlaced without knowing that:
Each consumer independently subscribes and reacts. Adding a new consumer (say, a Loyalty Service) requires zero changes to the Order Service.
Commands Create Explicit Coupling:
When you send a command, you explicitly know the receiver. The Checkout Service sending ProcessPayment knows:
This is tighter coupling, but it's intentional. For critical actions where you need confirmation and control, explicit coupling is appropriate.
| Dimension | Events | Commands |
|---|---|---|
| Knowledge of receiver | None—publish to topic | Explicit—send to specific handler |
| Adding new reactions | Consumer subscribes; no producer changes | New integration requires sender changes |
| Removing reactions | Consumer unsubscribes; no producer changes | Sender may need to handle absence |
| Schema changes | Consumers must adapt to producer | Sender must adapt to handler |
| Testing in isolation | Easy—publish events, verify output | Requires handler or mock |
While loose coupling is often desirable, it's not universally better. Commands provide clear responsibility, explicit failure handling, and guaranteed delivery acknowledgment. For critical financial transactions, legal actions, or anything requiring confirmation, command-style explicit coupling may be more appropriate than fire-and-forget events.
The structural patterns enabled by events and commands differ significantly:
Fan-Out Details:
With event fan-out, each consumer sees every event and decides independently how to react (or not). This creates maximum flexibility:
OrderPlaced (event)
├── Email Service → sends confirmation email
├── Inventory Service → reserves inventory
├── Analytics Service → updates metrics
├── Fraud Service → runs fraud check
└── [Future: Loyalty Service → awards points]
The Order Service publishes once. The broker delivers to all subscribers. Each consumer is isolated—a bug in the Fraud Service doesn't affect email delivery.
Point-to-Point Details:
With commands, you have explicit routing to a specific handler. This provides:
For work distribution (like processing payments), you might have multiple handler instances, but only one handles any given command (competing consumers pattern).
With a thorough understanding of the differences, here's a practical decision framework:
| Scenario | Use Event | Use Command |
|---|---|---|
| Something happened that others might care about | ✅ Announce it | |
| Need to notify multiple independent systems | ✅ Fan-out | |
| Want to record history for audit/replay | ✅ Events form immutable log | |
| Producer shouldn't know/care about consumers | ✅ Loose coupling | |
| Need to request a specific action | ✅ Direct request | |
| Need confirmation of success/failure | ✅ Response expected | |
| Action has a single responsible owner | ✅ Clear responsibility | |
| Need synchronous-style semantics over async | ✅ Request-response pattern |
Hybrid Patterns:
In practice, systems often use both. A common pattern:
This combines the explicit control of commands (for the critical action) with the loose coupling of events (for downstream reactions).
Ask yourself: Am I describing something that happened, or asking for something to happen? If it's past-tense fact, it's an event. If it's an imperative request, it's a command. The grammar usually reveals the truth.
Understanding the distinction helps you avoid common architectural mistakes:
If your 'event' starts with a verb like 'Send', 'Process', 'Create', 'Update'—it's probably a command in disguise. Events are named with past-tense verbs: 'Sent', 'Processed', 'Created', 'Updated'. Get the grammar right, and the design usually follows.
Let's see how events and commands work together in a realistic e-commerce order flow:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
// USER ACTION: Customer clicks "Place Order" // ─────────────────────────────────────────────────────────────────// STEP 1: COMMAND - PlaceOrder (explicit request to order service)// ─────────────────────────────────────────────────────────────────COMMAND: PlaceOrder { customerId: "cust_123", items: [...], shippingAddress: {...}, paymentMethod: "card_xyz"}→ Sent to: Order Service (single handler)→ Expected: Success/Failure response // Order Service validates, creates order, returns orderIdRESPONSE: { success: true, orderId: "ord_456" } // ─────────────────────────────────────────────────────────────────// STEP 2: EVENT - OrderPlaced (broadcast announcement)// ─────────────────────────────────────────────────────────────────EVENT: OrderPlaced { orderId: "ord_456", customerId: "cust_123", items: [...], totalAmount: 149.99, timestamp: "2025-01-08T10:30:00Z"}→ Published to: "order-events" topic→ Consumers: Email, Inventory, Analytics, Fraud (all independent) // ─────────────────────────────────────────────────────────────────// STEP 3: COMMAND - ProcessPayment (explicit request to payment service)// ─────────────────────────────────────────────────────────────────COMMAND: ProcessPayment { orderId: "ord_456", amount: 149.99, paymentMethod: "card_xyz"}→ Sent to: Payment Service (single handler)→ Expected: Success/Failure response // Payment Service charges card, returns resultRESPONSE: { success: true, paymentId: "pay_789" } // ─────────────────────────────────────────────────────────────────// STEP 4: EVENT - PaymentCompleted (broadcast announcement)// ─────────────────────────────────────────────────────────────────EVENT: PaymentCompleted { paymentId: "pay_789", orderId: "ord_456", amount: 149.99, method: "credit_card", timestamp: "2025-01-08T10:30:05Z"}→ Published to: "payment-events" topic→ Consumers: Order Service, Email Service, Accounting, Analytics // ─────────────────────────────────────────────────────────────────// STEP 5: COMMAND - ShipOrder (explicit request to fulfillment)// ─────────────────────────────────────────────────────────────────// Triggered by Order Service upon receiving PaymentCompletedCOMMAND: ShipOrder { orderId: "ord_456", items: [...], shippingAddress: {...}}→ Sent to: Fulfillment Service→ Expected: Success/Failure/TrackingNumber // ─────────────────────────────────────────────────────────────────// STEP 6: EVENT - OrderShipped (broadcast announcement)// ─────────────────────────────────────────────────────────────────EVENT: OrderShipped { orderId: "ord_456", trackingNumber: "TRK_123456", carrier: "FedEx", estimatedDelivery: "2025-01-12", timestamp: "2025-01-08T14:00:00Z"}→ Published to: "fulfillment-events" topic→ Consumers: Email Service, Customer App, AnalyticsPattern Analysis:
Notice the rhythm: Command → Event → Command → Event
This hybrid approach gives you:
The distinction between events and commands is fundamental to well-designed event-driven systems. Let's consolidate the key insights:
What's Next:
Now that we understand the fundamental distinction between events and commands, we'll explore the entities that send and receive these messages. The next page examines Producers and Consumers—the components that create events and react to them, including their design patterns, responsibilities, and best practices.
You now have a deep understanding of the difference between events and commands—the foundational distinction that shapes event-driven system design. This knowledge will guide your architectural decisions about when to announce versus when to request, and how to structure communication between services.