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

  1. Command Messages - Enterprise Integration Patterns’ canonical definition of commands
  2. Event Messages - Enterprise Integration Patterns’ canonical definition of events
  3. Message - Enterprise Integration Patterns’ canonical definition of a message.