In event-driven architecture, one of the first distinctions that matters is the difference between an event and a command.
At first, this sounds like naming.
One message is called OrderCreated. Another message is called CreateOrder.
How much difference can that really make?
In practice, quite a lot.
The difference between events and commands influences how services communicate, who owns a business action, how many systems should react, how you handle failure, and how tightly coupled your architecture becomes.
This is one of those small concepts that becomes more important the longer a system lives.
A command is a request to do something.
It is written in the imperative form.
For example:
CreateOrder
ReserveInventory
ChargePayment
SendEmail
CancelSubscription
GenerateInvoice
A command has intent. It asks another part of the system to perform an action.
That action may succeed, fail, or be rejected.
For example, ChargePayment may fail because the card is declined. ReserveInventory may
fail because the item is out of stock. CancelSubscription may fail because the subscription is
already cancelled.
A command usually has one logical owner.
If I send a ChargePayment command, I expect the Payment Service to handle it. Not five different
services. The Payment Service owns the rules around charging payments.
That is an important property of commands.
A command says:
Please do this.
And there is usually one service responsible for deciding whether that thing can be done.
An event is different.
An event is a fact about something that already happened in the system.
It is usually written in the past tense.
For example:
OrderCreated
InventoryReserved
PaymentSucceeded
PaymentFailed
EmailSent
SubscriptionCancelled
InvoiceGenerated
An event does not ask anyone to do anything.
It says:
This happened.
Other services may care about that fact and react to it, but the event itself is not a request.
For example, when PaymentSucceeded is published, several systems may be interested:
Order Service marks the order as paid
Email Service sends a receipt
Analytics Service updates revenue metrics
Accounting Service records the payment
Fraud Service updates risk signals
The Payment Service does not need to know all those consumers exist. It only publishes the fact that the payment succeeded.
That is where events become powerful.
They allow many parts of the system to react to the same business fact without tightly coupling the producer to every consumer.
Imagine a user places an order.
A command might look like this:
CreateOrder
This is a request.
The caller wants the Order Service to create an order. The Order Service validates the request, checks business rules, stores the order, and decides whether the command succeeds.
After the order is created, the Order Service can publish an event:
OrderCreated
Now other systems can react.
For example:
Payment Service starts payment flow
Inventory Service reserves stock
Email Service sends confirmation
Analytics Service updates reporting
The command initiated the action.
The event announced the result.
That distinction keeps the design cleaner.
One useful way to think about commands is that they can be refused.
For example:
CreateOrder
can be rejected if the basket is empty.
ReserveInventory
can be rejected if the product is out of stock.
ChargePayment
can be rejected if the payment provider declines the card.
CancelSubscription
can be rejected if the subscription has already ended.
Commands represent intent, but intent does not always become reality.
That is why commands often produce events as a result.
For example:
Command: ChargePayment
Possible events:
PaymentSucceeded
PaymentFailed
PaymentRejected
The command asks for something.
The event records what actually happened.
Events are different because they represent facts.
If a service receives OrderCreated, it should not think:
I reject that order creation.
The order has already been created.
The consumer may fail to process the event. It may retry later. It may move the message to a dead-letter queue if something goes wrong. But it should not treat the event as something it can approve or deny.
That decision has already happened in the service that owns the business action.
This is one reason why ownership matters so much.
The service that publishes an event should own the fact being published.
The Order Service can publish OrderCreated.
The Payment Service can publish PaymentSucceeded.
The Inventory Service can publish InventoryReserved.
Each service publishes facts from its own domain.
A command usually has one intended handler.
For example:
ChargePayment -> Payment Service
ReserveInventory -> Inventory Service
GenerateInvoice -> Billing Service
That does not mean there can never be internal delegation. But conceptually, there is one service responsible for handling the command.
This gives a clear ownership model.
If the payment was charged incorrectly, you know where to look.
If inventory reservation failed, you know which service owns that decision.
Commands are useful when you want a specific service to perform a specific action.
Events are different.
An event can have zero, one, or many subscribers.
For example:
UserRegistered
could be consumed by:
Email Service
Analytics Service
CRM integration
Notification Service
Recommendation Service
Audit log
The producer should not need to know this list.
That is one of the main benefits of event-driven architecture. New behavior can be added by adding a new consumer, instead of changing the service that produced the original event.
This is especially useful when business processes evolve over time.
Today, UserRegistered only sends a welcome email.
Tomorrow, it also creates a CRM contact.
Next month, it starts an onboarding workflow.
The User Service should not have to become responsible for all of that.
One common mistake is to use event names for things that are actually commands.
For example:
SendWelcomeEmail
UpdateSearchIndex
CreateInvoice
ReserveInventory
These are commands. They ask another system to do something.
Sometimes teams put these messages on a broker and call the system event-driven, but the design is still command-driven. It is just asynchronous command delivery.
That is not always wrong.
Asynchronous commands can be useful.
But it is important to be honest about what the message means.
There is a big difference between:
UserRegistered
and:
SendWelcomeEmail
UserRegistered is a fact. Any number of services can react to it.
SendWelcomeEmail is an instruction. It is meant for the Email Service.
Both messages can be sent through a queue or broker, but they are not the same concept.
Another problem is creating events that describe implementation details instead of business facts.
For example:
DatabaseRowInserted
OrderTableUpdated
CacheInvalidated
EmailJobQueued
Some technical events are useful inside infrastructure, but they are usually not good domain events.
A good domain event should mean something to the business.
For example:
OrderCreated
PaymentSucceeded
SubscriptionCancelled
BonusGranted
GameRoundCompleted
These events describe something meaningful that happened in the domain.
That makes them easier to understand, easier to consume, and more stable over time.
Technical details change often. Business facts usually change more slowly.
Naming messages well forces you to think clearly.
A command should usually be named with an imperative verb:
CreateOrder
ChargePayment
CancelSubscription
ReserveInventory
An event should usually be named in the past tense:
OrderCreated
PaymentCharged
SubscriptionCancelled
InventoryReserved
This is not just style. It helps developers understand the meaning of the message.
When I see CancelSubscription, I know something is being requested.
When I see SubscriptionCancelled, I know something has already happened.
That small difference changes how I write the handler.
For a command, I need to validate whether the action is allowed.
For an event, I need to react to a fact that already occurred.
In a real system, commands and events often work together.
For example:
CreateOrder command
OrderCreated event
ReserveInventory command
InventoryReserved event
InventoryReservationFailed event
ChargePayment command
PaymentSucceeded event
PaymentFailed event
CancelOrder command
OrderCancelled event
This makes the flow explicit.
Commands represent intent.
Events represent results.
That separation is useful in business workflows, especially when using patterns like sagas.
A saga might send commands to different services and wait for events in response.
For example:
Order Saga sends ReserveInventory
Inventory Service publishes InventoryReserved
Order Saga sends ChargePayment
Payment Service publishes PaymentSucceeded
Order Saga sends ConfirmOrder
Order Service publishes OrderConfirmed
In this model, commands move the process forward, and events report what happened.
Use a command when you want a specific service to do something.
For example:
Charge this payment
Reserve this stock
Generate this invoice
Cancel this subscription
Send this email
A command is a good fit when:
There is one clear owner
The action can succeed or fail
The sender expects a specific behavior
The receiver needs to validate business rules
Commands are often useful for orchestration, workflows, and service boundaries where intent matters.
Use an event when something already happened and other systems may care.
For example:
Payment succeeded
Order was created
User registered
Subscription was cancelled
Invoice was generated
An event is a good fit when:
The fact already happened
Multiple consumers may be interested
The producer should not know all consumers
The system benefits from loose coupling
Events are useful for integration, reporting, notifications, audit trails, projections, and downstream workflows.
Not every message fits perfectly into one category.
For example:
PasswordResetRequested
Is that a command or an event?
It depends on the meaning.
If it means “the user requested a password reset, and this is now a fact in the User Service,” then it is an event.
If it means “Email Service, please send a password reset email,” then it is a command hiding as an event.
The name alone is not enough. You need to understand ownership and intent.
A good question to ask is:
Is this message asking someone to do something, or is it announcing that something happened?
That question usually reveals the answer.
In interviews, this distinction is useful because it shows whether you understand event-driven architecture beyond the tooling.
It is easy to say:
We publish messages to Kafka.
But the better discussion is:
What do those messages mean?
Who owns them?
Can they be rejected?
Can multiple services consume them?
What happens if a consumer fails?
Is this a business fact or an instruction?
That is where the real architecture lives.
A system does not become event-driven just because it uses Kafka, RabbitMQ, SNS, SQS, or NATS.
It becomes event-driven when business facts are published as events and other services can react to those facts independently.
If I had to explain the difference in an interview, I would say:
A command is a request to perform an action, like CreateOrder or ChargePayment. It
usually has one logical handler, and it can succeed, fail, or be rejected. The receiving service owns the
decision.
An event is a fact that something already happened, like OrderCreated or
PaymentSucceeded. It is usually published in the past tense and can have multiple subscribers.
Consumers do not approve or reject the event. They react to it.
The distinction matters because commands create directed communication, while events allow loose coupling. If I want a specific service to do something, I use a command. If I want to announce a business fact that other services may care about, I publish an event.
In practice, both are often used together. A workflow may send commands to services and listen for events that describe the result.
The difference between events and commands looks small, but it shapes the whole system.
A command expresses intent.
An event records reality.
When those two ideas are mixed together, systems become harder to reason about. Services start reacting to messages that are actually instructions. Producers become coupled to consumers. Business flows become hidden inside naming conventions.
When the distinction is clear, the architecture becomes easier to understand.
You know who owns the action.
You know who owns the fact.
You know whether a message is asking for something or announcing something.
That clarity matters.
This post is part of my Backend Architecture Notes series. In the next post, I will look at eventual consistency, why it shows up in event-driven systems, and how to explain it without hand-waving.