Events vs Commands: The Distinction Everyone Always Forgets
Here’s the thing about event-driven architecture, everyone keeps shooting themselves in the foot by mixing up events and commands. I’ve seen this pattern dozens of times: a team starts with EventBridge, everything looks great, then six months later they’re drowning in hidden dependencies and wondering why their “decoupled” system requires 17 services to deploy together.
The root cause? They’re using events when they mean commands, and commands when they mean events.
Commands Request Action. Events Report Facts.
That’s it. But wow, does this simple idea change how you build systems.
graph LR
subgraph "Command Pattern"
A[Service A] -->|"ProcessPayment"| B[Payment Service]
A -.->|"Expects Response"| B
end
subgraph "Event Pattern"
C[Order Service] -->|"OrderCreated"| D[Event Bus]
D --> E[Service X]
D --> F[Service Y]
D --> G[Service Z]
C -.->|"No Knowledge of Consumers"| D
end
So What’s an Event, Really?
An event is just a record that something happened. Past tense. Done deal. No takesies-backsies.
Think “OrderCreated” not “CreateOrder”. The order exists now. It’s a fact. Other services can react to this fact, ignore it completely, maybe store it for later. But they can’t reject it - you can’t un-create an order that already exists in your database. Reality doesn’t have a rollback button.
When you publish an event, you’re broadcasting to whoever cares. EventBridge, SNS topic, Kinesis stream - pick your poison. The beautiful part? You have no idea who’s listening. And that’s exactly the point.
Events need to be self-contained too. Include enough context so consumers can do their thing without calling you back asking for more details. Oh, and name them in past tense: OrderCreated, PaymentProcessed, UserRegistered. Not CreateOrder. That’s a command.
Commands Are Different Beasts
A command is you asking someone to do something. It’s directed, it expects action, and yeah, it can totally fail.
“ProcessPayment” is a command. You’re asking a specific service to do specific work. That service can say no - maybe the card is declined, maybe the amount exceeds limits, maybe Mercury is in retrograde. Who knows.
With commands, you know exactly where you’re sending them. Into an SQS queue, direct Lambda invoke, whatever. The receiver can reject them based on business rules, validation, technical failures. And you better design for retries because networks are terrible and stuff happens.
The Test That Actually Works
Still confused? Ask yourself this one question:
Would the producer fail if nobody listened?
graph TD
subgraph "Event: Producer Succeeds Regardless"
A[Order Service] -->|"Create Order"| B[Database]
B -->|"Success"| C[Publish OrderCreated]
C -->|"Nobody Listening?"| D[✅ Order Still Created]
end
subgraph "Command: Producer Needs Response"
E[Payment Service] -->|"Send ChargeCard Command"| F[Queue]
F -->|"Nobody Processing?"| G[❌ Payment Fails]
end
When OrderService creates an order, what’s its actual job? To record that order. If it publishes “OrderCreated” and nobody subscribes? Whatever. Order still exists. OrderService did its job.
But PaymentService trying to charge a card? If nobody’s listening to that ChargeCard command, PaymentService has completely failed. No charge, no payment, unhappy customers.
It’s about understanding what each service actually owns. OrderService owns the order lifecycle - success means orders get recorded. PaymentService owns payment orchestration - if payments don’t process, it’s failed. FulfillmentService owns shipping, but OrderService doesn’t care if shipping is down. Not its problem.
Producer Intent: The Distinction Nobody Talks About
This is where people get confused. They think because someone reacts to an event, it must be a command. Wrong.
It’s About What the Producer Expects
When OrderService publishes “OrderCreated,” what’s its intent?
Event Intent:
- “This order now exists in my domain”
- “Other services might want to know”
- “I’ve done my job regardless of what happens next”
NOT:
- “Someone must ship this”
- “Inventory better reserve stock”
- “Payment processing needs to happen”
The producer publishes events to announce state changes, not to orchestrate behavior. Yeah, FulfillmentService will probably react by shipping the order. But that’s FulfillmentService’s decision, not OrderService’s expectation.
Why This Matters
Say you’ve got an event “UserRegistered”. Three services subscribe:
- EmailService sends a welcome email
- AnalyticsService tracks the signup
- MarketingService adds them to a campaign
Tomorrow you add:
- ComplianceService for GDPR tracking
- RecommendationService to prep suggestions
With true event intent, UserService doesn’t know or care. It announced a fact. What happens next is not its problem.
But if UserService was secretly expecting that welcome email to be sent? That’s command thinking. You’ll find out the hard way when EmailService goes down and new users complain about missing emails, and suddenly UserService is coupled to EmailService’s availability.
The “But Someone Has To React!” Trap
I hear this all the time, “But if nobody processes OrderCreated, we don’t ship orders!”.
Yes, and?
That’s a business process problem, not OrderService’s problem. OrderService owns order creation. FulfillmentService owns shipping. If shipping is critical to your business (it probably is), then make sure FulfillmentService is running. But don’t make OrderService responsible for it. This isn’t academic philosophy. It’s about boundaries. When services start caring about what happens to their events, you get:
- Synchronous checkpoints (“Did shipping receive this?”)
- Retry logic in the wrong places
- Services that can’t evolve independently
- “Decoupled” systems that aren’t
Events Enable Capabilities, Not Obligations
When you publish an event, you’re enabling other services to build on top of your domain. You’re not obligating them to do anything.
Think of it like this: A newspaper publishes news (events). Readers might:
- Make investment decisions
- Plan their commute differently
- Discuss it at dinner
- Line their birdcage
The newspaper doesn’t care. It reported facts. What happens next is not its concern or responsibility. Same with your services. OrderService reports “OrderCreated.” What the rest of your system does with that information is their business.
Clearing up some Jargon
“Messaging” vs “Eventing”
Teams struggle with these terms constantly. “Messaging” and “eventing” get thrown around like they’re totally different things, but they’re not.
When someone says “messaging” they usually mean point-to-point communication. SQS queues. One sender, one receiver. “Send a message to the payment service.”.
When they say “eventing” they mean broadcasts. EventBridge. Fire-and-forget patterns. “Publish an event that payment completed.”.
But here’s what’s actually happening: both are messages being passed between services. The real difference? Intent and coupling.
The fix is simple:
- Say “command” when you mean directed requests for action
- Say “event” when you mean broadcast facts about what happened
- Stop using “messaging” and “eventing” in design discussions
Once you make this switch, architecture discussions get way clearer. No more “should we use messaging or eventing?” Instead it’s “is this a command or an event?” Much better.
Choreography vs Orchestration
graph TD
subgraph "Orchestration Pattern"
O[Order Orchestrator] -->|"1. CreateOrder"| OS[Order Service]
O -->|"2. ReserveInventory"| IS[Inventory Service]
O -->|"3. ProcessPayment"| PS[Payment Service]
O -->|"4. ArrangeShipping"| SS[Shipping Service]
style O fill:#ff9999
end
subgraph "Choreography Pattern"
OS2[Order Service] -->|"OrderCreated"| EB[Event Bus]
EB --> IS2[Inventory Service]
IS2 -->|"InventoryReserved"| EB
EB --> PS2[Payment Service]
PS2 -->|"PaymentProcessed"| EB
EB --> SS2[Shipping Service]
style EB fill:#99ff99
end
This confuses everyone. You design a choreographed system, but that doesn’t make it orchestrated.
With orchestration, something’s in charge. Step Functions maybe, explicitly controlling who does what when. One brain knowing the whole process.
Choreography? Nobody’s in charge. Services just react to events, doing their part. The business process emerges from everyone doing their own thing.
The weird part: You plan the choreography at design time (“FulfillmentService will subscribe to OrderCreated”), but at runtime OrderService has no clue FulfillmentService exists. You designed the dance, but nobody’s leading it.
Making This Work on AWS
Events Love EventBridge or Kinesis
EventBridge is perfect for broadcasting facts. Multiple services can subscribe without the publisher knowing. New consumers don’t break anything. Rules route based on content. You can even archive and replay for debugging when things go sideways.
Cost? About $1 per million events. Unless you’re Netflix, that’s nothing.
When do you actually need Kinesis?
Only when EventBridge’s limits start hurting:
- You need more than 10,000 events/second sustained (EventBridge’s soft limit)
- Order matters within a partition (Kinesis guarantees order, EventBridge doesn’t)
- You’re doing stream processing with windowing or aggregations
- You need to replay the exact sequence of events
For 95% of event-driven systems, EventBridge is simpler and cheaper. Don’t reach for Kinesis just because it sounds cooler.
Commands Love SQS
SQS is perfect for directed commands. Clear sender, clear receiver. Built-in retries with exponential backoff (because failures happen). Dead letter queues for the really stubborn failures. FIFO when order matters.
Cost? $0.40 per million messages. Even cheaper.
Where Things Go Wrong
Hidden Commands in Event Clothing: You publish “OrderCreated” but you’re really expecting InventoryService to reserve stock. If inventory doesn’t react, orders fail. That’s not an event, that’s a command wearing a disguise.
graph LR
subgraph "Hidden Commands"
A[Order Service] -->|"OrderCreated"| B[Event Bus]
B --> C[Inventory Service]
C -.->|"❌ Must Reserve Stock"| D[Hidden Expectation]
style D fill:#ffcccc
end
Event Chain Hell: OrderCreated triggers InventoryReserved triggers PaymentRequested triggers… congratulations, you’ve built distributed orchestration with extra steps and no error handling.
graph LR
subgraph "Event Chain"
E[Order] -->|"OrderCreated"| F[Inventory]
F -->|"InventoryReserved"| G[Payment]
G -->|"PaymentProcessed"| H[Shipping]
H -->|"OrderShipped"| I[Notification]
E -.->|"Distributed Orchestration"| I
style E fill:#ffcccc
style I fill:#ffcccc
end
The “It’s Just a Notification” Excuse: No. If your system breaks when nobody processes your “notification,” it’s a command. Call it what it is.
How to Choose
graph TD
A[Message to Send] --> B{Who needs to know?}
B -->|"One specific service"| C[Command]
B -->|"Anyone interested"| D[Event]
A --> E{Can it fail with business logic?}
E -->|"Yes"| F[Command]
E -->|"No, already happened"| G[Event]
A --> H{What if nobody listens?}
H -->|"System breaks"| I[Command]
H -->|"Life goes on"| J[Event]
A --> K{What's my intent?}
K -->|"Request action"| L[Command]
K -->|"Share information"| M[Event]
style C fill:#ff9999
style F fill:#ff9999
style I fill:#ff9999
style L fill:#ff9999
style D fill:#99ff99
style G fill:#99ff99
style J fill:#99ff99
style M fill:#99ff99
Ask these questions. The answers tell you everything:
- Who needs to know? One service = command. Anyone who wants = event.
- Can it fail? If business logic can reject it, it’s a command. If it already happened, event.
- What if nobody listens? System breaks = command. Life continues = event.
What’s my intent? Am I requesting action or sharing information? This is the big one people miss.
Why This Actually Matters
Get this wrong and you’re in for pain. “Decoupled” services that need synchronized deployments. Can’t add new features without touching five other services. Event storms that are just spaghetti code with extra steps.
Get it right? Services that genuinely don’t know about each other. New capabilities without modifying existing code. Systems that degrade gracefully instead of falling over completely.
Every time you’re about to send something between services, pause. Ask yourself: “Am I requesting action, or stating a fact?”
The answer changes everything about how you build it. Everything.
What’s the worst “event” you’ve seen that was actually a command in disguise? I’d love to hear your horror stories in the comments. We’ve all been there.
References
- Command Messages - Enterprise Integration Patterns’ canonical definition of commands
- Event Messages - Enterprise Integration Patterns’ canonical definition of events
- Message - Enterprise Integration Patterns’ canonical definition of a message.