Create your own
Lesson illustration

Classifying Command, Event, and Document Messages

Welcome. This course will build from the language of event-driven architecture into RabbitMQ, Kafka, CDC with Debezium, reliability patterns, and ultimately the kind of trade-off-driven system-design answers expected of a Technical Lead. This first module establishes the concepts that prevent many design mistakes later: before deciding on a broker, topic, queue, or delivery guarantee, decide what each message means.

In this lesson, you will classify messages as commands, events, or documents by examining their intent and the bounded context that owns their contract. The serialization format does not determine the type: all three may be JSON, Avro, Protobuf, an HTTP body, a RabbitMQ message, or a Kafka record.


Message type is about intent, not transport

A message is a bundle of data, but its speech act matters:

  • Is the sender asking a particular system to do something?
  • Is the sender reporting something that has already happened?
  • Or is the sender simply providing a structured set of data for another system to use?

Those correspond to commands, events, and documents.

Introduction to Message Construction - Enterprise Integration Patterns

Read this concise introduction from Enterprise Integration Patterns. It gives the foundational distinction: messages may invoke behavior, transfer data, or notify others of a change.

Read the complete opening paragraph, beginning with the three intents. Focus especially on the final sentence describing what the sender does not prescribe for document and event messages.

A useful test is to remove all technology words:

“A Kafka event,” “a RabbitMQ command,” or “an HTTP document” is imprecise language. Kafka, RabbitMQ, and HTTP are transports. The message’s meaning comes from what it asks, asserts, or supplies.

For example, these payloads could contain nearly identical fields:

{
  "productId": "P-1042",
  "price": 49.99,
  "currency": "USD"
}

Yet they can mean very different things:

  • Command: “Catalog service, set this product’s price to 49.99 USD.”
  • Event: “The Catalog service changed this product’s price to 49.99 USD.”
  • Document: “Here is the current product-pricing record.”

The fields are not enough to classify the message. Ask what relationship the sender expects with the receiver.


Commands: requests for a capability

A command asks a system to perform an action. It is imperative: the sender wants a particular capability to be invoked.

Typical command names use a verb followed by a noun:

  • UpdatePrice
  • PlaceOrder
  • ProcessPayment
  • ReserveInventory
  • DeactivateAccount

A command is addressed to one logical owner of the capability. There may be multiple technical instances of the handler for scaling and high availability, but there should be one business authority that decides whether and how the instruction is executed.

Consider an online store:

{
  "commandId": "8a4e...",
  "productId": "P-1042",
  "newPrice": 49.99,
  "currency": "USD",
  "requestedBy": "admin-27"
}

If this is an UpdatePrice command, it belongs to the Catalog bounded context because Catalog owns pricing rules. The command sender might be an admin API today and a bulk-pricing service tomorrow. Both must obey the contract that Catalog defines, because Catalog alone knows what it needs to validate and perform the price update.

That gives commands a distinctive ownership model:

QuestionCommand answer
What is its intent?Request an action
When does it exist?Before the requested action is accepted and completed
Who defines its schema?The receiving capability’s bounded context
How many business handlers?One
Can it be rejected?Yes; rejection is normal
Naming styleImperative, often verb + noun

A command may fail because a product does not exist, the requester lacks authorization, the price violates a rule, or a duplicate command was detected. The fact that a message was delivered does not mean its business action succeeded.

This is especially important in asynchronous designs. A producer can successfully publish ProcessPayment, while the Payment service later rejects the command. Publication is transport success; it is not business success.

Commands & Events: What's the difference?

Watch “Commands & Events: What's the difference?” from CodeOpinion for a practical explanation of intent, single-command handling, and the reversal of contract ownership for events.

Watch commands for the command’s role as a request to invoke behavior and why the handler’s logical boundary owns its schema. Then watch events for publisher ownership and independent subscribers. Finish with the workflow, which connects an incoming request, a command handler, and a resulting event.

A command is not automatically synchronous

A REST call such as POST /orders can be a command, and a RabbitMQ message on a queue can be a command. The communication mode is separate from intent.

For instance, an Order API may accept a checkout request, persist an “accepted for processing” state, enqueue PlaceOrder, and immediately return a tracking identifier. The command is still a request directed at the Order service; it simply does not require the original caller to wait for final completion.

A common design error is broadcasting a command such as ReserveInventory to several services and letting each decide whether to act. That creates ambiguous authority and risks duplicate or conflicting work. If several services should independently react, the initiating service should usually publish a fact as an event, and each subscriber chooses its own response.


Events: facts published by the system that experienced them

An event states that something meaningful has already happened. It does not tell its consumers what to do.

Typical event names are in the past tense:

  • PriceUpdated
  • OrderPlaced
  • PaymentAuthorized
  • InventoryReserved
  • AccountDeactivated

Events are not addressed to a particular receiver. They are published for any interested party. There may be no current subscribers, one subscriber, or many independent subscribers. Adding a new subscriber should not require the event publisher to change its business logic.

The contract ownership is the reverse of a command:

QuestionEvent answer
What is its intent?State a business fact
When does it exist?After the occurrence has been committed or otherwise made true
Who defines its schema?The bounded context that authoritatively experienced and published the fact
How many subscribers?Zero, one, or many
Can consumers reject the fact?No; they can fail to process it, but cannot make the fact untrue
Naming stylePast tense

Suppose the Catalog service has validated and committed a new price. It can now publish:

{
  "eventId": "c124...",
  "eventType": "PriceUpdated",
  "productId": "P-1042",
  "oldPrice": 44.99,
  "newPrice": 49.99,
  "currency": "USD",
  "occurredAt": "2025-03-08T10:15:30Z"
}

The Basket service may update cached basket prices. An analytics service may record the change. A search-index service may update a product display. Catalog did not command any of those actions. It simply declared a fact it owns: the price changed.

The Catalog microservice receives an `UpdatePrice` command, updates its own database, then publishes a `PriceUpdated` event through an event bus. The Basket microservice and other services independently consume that fact and update their own state, demonstrating command handling followed by event publication.

The architecture in the image contains a crucial boundary:

  1. UpdatePrice is a command directed to Catalog.
  2. Catalog updates the database it owns.
  3. PriceUpdated is an event published after the change.
  4. Other services decide for themselves whether and how to react.

Later lessons will examine how to make the database update and event publication reliable. For now, preserve the conceptual rule: do not publish PriceUpdated merely because somebody requested a price update. Publish it because Catalog has established that the update happened.

“But my event has only one consumer”

That does not make it a command. The number of consumers is evidence, not the definition.

A message remains an event if it says, “this fact occurred,” even when only the Basket service currently subscribes. Conversely, a message remains a command if it requests work from one authoritative handler, even if a broker happens to route it through a topic-like mechanism.

Consumer failure does not invalidate an event

If the Basket service cannot process PriceUpdated because its database is unavailable, the price change in Catalog is still true. Basket must retry, recover, or reconcile its state. This is the beginning of eventual consistency: services do not all reflect the new fact at precisely the same instant.

Do not confuse this with an event being “accepted.” A consumer may validate the event’s technical shape, detect an incompatible schema, or route a malformed payload for investigation. But it cannot legitimately decide: “I decline this price update, therefore the Catalog price was never updated.”


Document messages: data offered without prescribing behavior

A document message transfers a data structure. The sender supplies information but does not instruct the receiver to execute a specific action, and does not necessarily announce a discrete business occurrence.

Examples include:

  • a customer profile sent to an analytics platform;
  • a daily product catalog export;
  • a supplier’s inventory feed;
  • a complete account snapshot made available to a reporting service;
  • an invoice document sent to a document archive.

For example:

{
  "customerId": "C-822",
  "name": "Asha Rao",
  "contact": {
    "email": "asha@example.com",
    "phone": "+1-555-0100"
  },
  "addresses": [
    {
      "type": "shipping",
      "city": "Boston",
      "country": "US"
    }
  ]
}

If a CRM system sends this as CustomerProfileDocument, the message says: “Here is a representation of customer data.” The receiving analytics or reporting system decides whether to store it, enrich it, ignore some fields, or use it as input to a pipeline. No behavior is mandated by the message itself.

Documents commonly use a source-owned or jointly governed representation:

  • When a service exposes its own record or export, the source system normally owns the meaning and evolution of that data representation.
  • When several organizations use a shared canonical document format, ownership must be explicitly governed. It is often a shared contract, not a clean one-way ownership relationship like a command or an event.
  • A receiver owns its local projection, transformation, and storage model. It should not quietly assume ownership of the producer’s document schema.

This is less rigid than command and event ownership, so in an interview, state the assumption clearly: “The Customer domain is authoritative for the customer document; consumers must tolerate compatible evolution.”

Document versus event: the subtle distinction

Events often carry substantial data. Therefore, “it contains an object” does not make a message a document.

Compare these two messages:

{
  "eventType": "CustomerAddressChanged",
  "customerId": "C-822",
  "oldAddress": {
    "city": "Boston"
  },
  "newAddress": {
    "city": "Chicago"
  },
  "occurredAt": "2025-03-08T10:30:00Z"
}
{
  "documentType": "CustomerProfile",
  "customerId": "C-822",
  "name": "Asha Rao",
  "addresses": [
    {
      "type": "shipping",
      "city": "Chicago"
    }
  ]
}

The first says a specific change occurred at a point in time. It supports reactions such as “recalculate tax” or “notify the fraud system.” The second supplies the current representation of a customer. It supports data exchange, indexing, or batch processing.

A useful diagnostic is this:

  • If the message must be retained because the occurrence itself matters, it is probably an event.
  • If the message is mainly valuable because it is the latest data representation, it is probably a document.

Do not over-apply this distinction. A complete snapshot can be included in an event, and a document may be sent after a change. Classify the message by the contract’s purpose, not only by the shape of its payload.


A repeatable classification method

When reviewing an API, a Kafka topic, or a RabbitMQ exchange, use this sequence.

1. State the message in plain language

Avoid the type name first. Translate it into a sentence:

  • “Payment service, charge this card” suggests a command.
  • “A payment was authorized” suggests an event.
  • “Here is the payment settlement file” suggests a document.

2. Locate it in time

A request exists before the outcome is known. A fact exists after the outcome occurred.

Timing questionLikely classification
Can the receiving business service still say no?Command
Would saying no deny an already-recorded fact?Event
Is the message primarily a representation rather than a request or occurrence?Document

3. Identify the intended authority

Ask, “Who has the right to define this contract?”

  • The system that can perform the requested capability owns a command.
  • The system that is authoritative for an occurrence owns an event.
  • The provider of the data representation generally owns a document, unless a governed shared contract has been established.

4. Identify the expected audience

  • One business handler should execute a command.
  • Any interested service may consume an event.
  • Documents can be sent point-to-point or shared with several receivers; recipient count is not a decisive feature for documents.

5. Check the name last

Naming should confirm your classification, not substitute for it:

IntentGood nameMisleading name
Ask Catalog to change a priceUpdatePricePriceUpdated
State that Catalog changed a pricePriceUpdatedUpdatePrice
Send current supplier dataSupplierCatalogDocumentSupplierCatalogUpdated when no update occurrence is being communicated

Names matter because they encode temporal and ownership expectations. A past-tense event name tells consumers they are reacting to an established fact. An imperative command name tells its handler it is responsible for an attempted action.


A realistic order workflow

Let’s classify a familiar workflow without relying on broker terminology.

A web client submits checkout details. The Order service receives a request to place an order. Internally, it may issue PlaceOrder to its own asynchronous command handler.

  • PlaceOrder is a command. Order owns the behavior and can reject it due to invalid items, an expired cart, or failed validation.

After successfully creating the order, Order publishes OrderPlaced.

  • OrderPlaced is an event. Order owns the fact. Billing, Inventory, Shipping, and Notifications may independently subscribe.

Inventory receives OrderPlaced and decides it must reserve stock. It then handles ReserveInventory.

  • ReserveInventory is a command. Inventory owns reservation policy and may reject it when stock is insufficient.

If the reservation succeeds, Inventory publishes InventoryReserved.

  • InventoryReserved is an event. Order may observe it and move the order into a confirmed state; Shipping may prepare fulfillment.

Separately, a reporting team might receive a nightly OrderExportDocument containing all orders changed that day.

  • OrderExportDocument is a document. It provides structured data for reporting, without commanding the reporting system or representing one particular business occurrence.

This distinction is the basis for later choices:

  • RabbitMQ work queues often carry commands to a competing set of handler instances.
  • Kafka topics often distribute events to independently evolving consumers.
  • CDC frequently produces row-change records that need careful interpretation: a database change record is not automatically a well-designed domain event.
  • Contract evolution, retries, deduplication, ordering, and observability all depend on knowing the message’s intended role.

Common interview traps

“Every message on a topic is an event.”
False. A topic is a transport construct. A command may be routed through a topic, although that choice needs a clear single-handler rule.

“An event tells the next service what to do.”
Usually false. OrderPlaced states a fact. Inventory independently decides whether that fact should cause inventory reservation. If Order needs Inventory specifically to reserve stock, it sends ReserveInventory to Inventory.

“A failed consumer can reject an event.”
False. It can fail its own processing and require retry, compensation, or reconciliation. The historical fact remains.

“A message with a large payload is a document.”
False. An event may carry a full state snapshot, while a document may be very small. Intent determines the classification.

“The sender owns every message contract.”
Not for commands. The receiving capability defines what information it requires to perform the requested behavior. For events, the authoritative publisher owns the fact and event contract.

For a concise Technical Lead answer, use this structure:

“I classify by intent and authority, not by JSON shape or broker. A command requests behavior from one authoritative handler, which owns the command schema and may reject it. An event is a past-tense fact published by the owning domain for zero or many independent consumers. A document transfers a structured representation without prescribing an action; its schema is normally owned by the data provider or explicitly jointly governed.”


The essential distinction is simple but operationally powerful:

  • Commands request an action and belong to the service that performs it.
  • Events report a completed fact and belong to the service that experienced it.
  • Documents provide structured data without requiring a particular action; their representation is typically owned by the data source or governed collaboratively.

When you inspect a message, first ask: What is the sender trying to accomplish, and which bounded context has the authority to define this message? That answer is stronger than any naming convention or broker configuration.

Next, you will use this vocabulary to redesign a synchronous microservice interaction into asynchronous messaging and identify exactly which forms of coupling are removed—and which ones remain.

Can't find a good explanation? Sign up and we'll make it for you

Sign up