What an Event Really Is

A practical lesson on what counts as an event, what does not, and why precise vocabulary matters in event-driven architecture.

An event is a record that something already happened. That sounds simple, but many event-driven systems become confusing because teams blur the line between facts, commands, tasks, notifications, and logs. Once that vocabulary blurs, contracts become unstable. Producers start publishing internal implementation noise, while consumers start treating every message as an instruction. A strong event model begins by keeping these concepts separate.

The most useful mental test is this: if the message were stored for a year and rediscovered later, would it still read as a fact about a change that occurred? If the answer is yes, it is probably an event. If the message mainly tells another system what it should do next, it is probably a command or task. That distinction matters because facts create optional downstream reaction, while commands create directional dependency.

    flowchart TD
	    A["Message to classify"] --> B{"Describes a completed fact?"}
	    B -->|Yes| C["Likely an event"]
	    B -->|No| D{"Tells another system what to do?"}
	    D -->|Yes| E["Likely a command or task"]
	    D -->|No| F{"Only technical record or observability output?"}
	    F -->|Yes| G["Likely a log or metric event, not a domain event"]
	    F -->|No| H["May be a notification or weakly modeled message"]

What to notice:

  • the classification depends on meaning, not transport
  • a message sent through a broker is not automatically an event
  • domain vocabulary matters because consumers inherit that meaning

Events vs Commands

An event says something happened. A command says something should happen. order.placed is an event. create-invoice is a command. Commands are not bad. They are often the correct pattern when one service is explicitly asking another service to perform work. The problem begins when a system publishes commands while pretending they are events. That creates fake decoupling. The producer still expects a specific downstream behavior, but the contract is dressed up as a fact.

This distinction shapes system ownership. A true event allows multiple consumers to react in their own ways. One consumer might update a projection, another might trigger a workflow, and another might feed analytics. A command is narrower. It usually names one intended action and one intended responsibility path.

 1{
 2  "eventName": "order.placed",
 3  "eventId": "evt_01JQB4A4BY9C",
 4  "occurredAt": "2026-03-23T20:10:00Z",
 5  "data": {
 6    "orderId": "ord_48392",
 7    "customerId": "cus_1221",
 8    "totalAmount": 149.95,
 9    "currency": "USD"
10  }
11}
1{
2  "commandName": "create-invoice",
3  "requestId": "req_01992",
4  "data": {
5    "orderId": "ord_48392"
6  }
7}

The reader should notice that the first payload describes a completed business fact. The second asks another system to perform a specific action.

Events vs Requests

Requests usually expect timely answers. An HTTP call to validate inventory or authorize payment is a request. It may be synchronous or asynchronous internally, but it still represents direct interaction between a caller and a callee. Events, by contrast, do not require every consumer to answer the producer immediately. That is why request-driven and event-driven styles often coexist in strong systems rather than replacing each other.

If the business flow cannot continue without an answer, the system is still request-driven at that step, even if a message broker sits in the middle. That does not invalidate the design, but it does change the trade-off discussion.

Events vs Logs, Tasks, and Notifications

Logs record technical observation for humans or observability tools. They may mention the same underlying business action as an event, but they serve a different purpose. A task is a unit of work to be executed, often by a worker pool or scheduled engine. A notification tells consumers that something changed, but it may omit the business-rich state needed to act independently.

These distinctions matter because they affect contract stability and downstream autonomy:

  • logs optimize for debugging, not domain meaning
  • tasks optimize for work execution, not shared fact distribution
  • notifications optimize for light signaling, not necessarily for consumer independence
  • events optimize for recording meaningful change that others may react to

Why Precise Vocabulary Matters

Teams often underestimate how much architectural confusion begins as naming confusion. If one service emits customer.updated for every row change, another interprets it as a billing-status change, and a third treats it as a support-profile sync trigger, the event contract has already become ambiguous. Precision in event vocabulary reduces hidden coupling because consumers know what fact the producer is claiming.

That precision also improves governance. Schema review, cataloging, replay safety, and consumer onboarding all become easier when event names reflect real business meaning. By contrast, a transport filled with vague messages becomes difficult to evolve because nobody knows which consumer inferred which meaning.

Common Mistakes

  • publishing commands with event-style names
  • treating every queue message as a domain event
  • emitting vague catch-all messages such as updated
  • using database row change language when the business fact is more specific
  • confusing observability logs with integration contracts

These mistakes often start as “temporary simplifications.” In practice, they become the permanent semantics consumers build around.

Design Review Question

A team wants to publish user.updated for any profile, role, or status change because “consumers can inspect the payload and decide what they care about.” Is that a strong event?

Usually no. The stronger answer is that the event collapses several different business facts into one vague label. Consumers now need to infer meaning from internal field changes, which creates coupling and weakens contract stability. Separate facts such as user.email.changed, user.role.changed, or user.account.suspended are often more useful and safer to evolve.

Quiz Time

Loading quiz…
Revised on Thursday, April 23, 2026