Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Event

An immutable record that something happened at a particular point in time — the vocabulary by which one part of a system tells the rest what just occurred without telling anyone in particular.

Concept

Vocabulary that names a phenomenon.

Understand This First

  • Protocol — event delivery systems rely on protocols for publishing, subscribing, and acknowledging events.
  • API — events are often delivered through APIs (webhooks, streaming endpoints).

What It Is

An event is an immutable record of something that happened. A user clicked a button. A payment cleared. A sensor crossed a temperature threshold. A file finished uploading. In each case the event captures three things: a name in the past tense (OrderPlaced, UserSignedUp, TemperatureExceeded), a timestamp, and whatever payload a downstream reader needs to make sense of the fact. The event is not a request to do anything; it is a statement that something already happened.

The cleanest way into the concept is the distinction between an event and a command. A command says “do this”: ChargeCustomer, SendEmail, ReserveInventory. It is addressed to a particular handler, it expects an outcome, and it can succeed or fail. An event says “this happened”: OrderPlaced. It is not addressed to anyone in particular, it is past tense, and it cannot fail because it is a record of fact. The two can carry identical payloads and yet shape the system around them very differently: a command-shaped system has senders calling receivers; an event-shaped system has emitters announcing facts that any number of subscribers may react to or ignore.

Events come in a small number of recognizable shapes, and the vocabulary of those shapes is part of the concept itself. Martin Fowler’s 2017 catalog names four uses people pack into “event-driven,” and each one is a distinct way of using events:

  • Event notification. The event announces that something happened and carries just enough identifying information for an interested subscriber to go look up the rest. OrderPlaced { orderId: 7421 } invites the billing service to fetch the order details from somewhere authoritative.
  • Event-carried state transfer. The event carries the full payload, so subscribers can act on it without calling back to the source. OrderPlaced { orderId, customer, items, total } is self-contained; the inventory and notification services never need to ask the order service for more.
  • Event sourcing. The event log is the system’s source of truth. Current state is derived by replaying the events from the beginning. OrderPlaced, OrderShipped, OrderRefunded are not after-the-fact records of state changes — they are the state changes.
  • CQRS. The system separates the model used for changing state (commands and the events they produce) from the model used for reading state (queries served from materialized views). Events are the bridge between the write side and the read side.

These four are often confused for one another, especially in design conversations, because they all involve events flowing between components. Naming which shape a particular system uses is the first move in any honest event-driven design discussion.

A handful of supporting terms travel with the concept and are worth holding precisely. Publisher and subscriber (or producer and consumer) name the two ends of an event flow; the publisher emits, the subscriber listens. A broker is a piece of infrastructure that holds events between publisher and subscriber and decouples their schedules (Kafka, RabbitMQ, NATS, and AWS EventBridge are common examples). Idempotent describes a subscriber that can process the same event more than once without changing the system’s eventual state, which matters because most brokers guarantee at-least-once delivery, not exactly-once. Ordering is the property of whether events arrive in the sequence they were emitted; in single-process systems it is given, in distributed systems it is a choice with costs.

Why It Matters

Software that runs on more than one machine needs vocabulary for things that happen, because the alternative is for one component to call another component directly and wait for an answer. Direct calls bind sender and receiver tightly: every new receiver demands a change to the sender, every failure of the receiver becomes a failure of the sender, and the system’s behavior is buried in the call graph rather than declared anywhere readable. Events break that binding. The publisher announces a fact; whoever cares can listen; whoever doesn’t can ignore. The system’s behavior becomes the union of the subscribers, and that union can grow without touching the publisher at all.

The concept also matters for agentic systems specifically. An AI agent’s work is naturally event-shaped: TaskReceived, PlanGenerated, ToolCalled, ResultReceived, ResponseDelivered, BudgetExceeded, HumanReviewRequested. Each of these is a fact that something happened at a particular step. When the agent’s progress is modeled as a stream of events, the workflow becomes observable (a monitor subscribes), debuggable (the events are the trace), and extensible (a new step is a new subscriber). When the same workflow is modeled as a chain of direct calls, none of those properties come for free; each one has to be retrofitted, usually painfully.

Three failure modes keep recurring when the concept is missing or imprecise. The first is the event/command muddle: ChargeCustomer published as if it were an event, where multiple subscribers all try to charge the customer, or OrderPlaced sent as a command to a single handler that owns “what to do when an order is placed,” tightly coupling the next ten things that have to happen. The fix is to name the distinction and respect it: events are facts in the past tense, addressed to no one; commands are requests in the imperative, addressed to a handler.

The second is the invisible chain. A team adds a fourth subscriber to OrderPlaced, which emits LoyaltyPointsAwarded, which fires a TierChanged event, which triggers a MarketingEmailRequested command. Six months later nobody on the team can answer “what happens when an order is placed?” because the answer lives in subscriber registrations scattered across services. The fix is not to ban event chains, which are often the right shape, but to recognize that event-driven systems trade ease of extension for ease of tracing, and to invest in the tracing (correlation IDs, distributed tracing, event-flow diagrams) the architecture is asking for.

The third is the delivery-guarantee assumption. A subscriber written as if events arrive exactly once and in order quietly corrupts data the first time the broker redelivers a message under load, or the first time two publishers commit out of order, or the first time a network partition delays one stream. Every event-driven system inherits whatever delivery semantics its transport offers (at-most-once, at-least-once, exactly-once, ordered-per-partition, totally-ordered), and a subscriber that doesn’t know which it has is a subscriber that will be debugged at 3 a.m. The concept of an event is incomplete without the concept of its delivery guarantee.

How to Recognize It

A few signs reliably tell you that what you’re looking at is an event (or that what you’re designing should be one):

  • Past-tense names. OrderPlaced, UserSignedUp, FileUploaded, PaymentSettled. If the natural name is in the past tense, the thing being named is a fact, and the right shape is probably an event. If the natural name is imperative (ChargeCustomer, SendEmail), the right shape is probably a command.
  • No designated receiver. The publisher emits and walks away. There is no acknowledgement, no return value, no expectation that anything in particular will happen next. Whoever cares, listens; whoever doesn’t, doesn’t. If the emitter is waiting for a result, you’re probably looking at a request/response call, not an event.
  • Publish/subscribe vocabulary in the code. emit, publish, subscribe, on, addListener, dispatch, channel sends, topic names, broker URLs. These are syntactic admissions that the system is event-shaped.
  • A log that grows monotonically. A Kafka topic, an EventBridge bus, an append-only events table, a webhook delivery archive. Events accumulate; they are not overwritten. The log itself is often more durable and more trusted than any downstream state derived from it.
  • Multiple unrelated reactions to one fact. Placing an order charges the card, reserves inventory, sends an email, awards loyalty points, updates analytics — none of which know about each other. That fan-out is the diagnostic signature of an event-driven design.

The four shapes from Fowler’s taxonomy are usually identifiable from a few minutes with the code. Tiny payloads with IDs and timestamps suggest event notification. Fat payloads carrying full domain state suggest event-carried state transfer. An events table that is the system’s primary store suggests event sourcing. Separate write models and read models, bridged by events, suggests CQRS. A codebase that mixes shapes (notification on one bus, state transfer on another, sourcing inside one bounded context) is normal at runtime but worth naming carefully in design discussions, because each shape has its own failure modes and its own operational vocabulary.

How It Plays Out

A team builds an e-commerce system. When an order is placed, the system publishes an OrderPlaced event. The billing service listens and charges the customer. The inventory service listens and reserves the items. The notification service listens and sends a confirmation email. None of the three services know about each other; they only know about the event. When the team later adds a loyalty-points service, they subscribe it to OrderPlaced without modifying any existing code. The publisher is unaware that the system grew a new reaction; the new subscriber is unaware that three others were already in place. That mutual unawareness is the property events purchase.

Tip

When designing an agentic workflow, model the agent’s progress as a sequence of events: TaskReceived, PlanGenerated, ToolCalled, ResultReceived, ResponseDelivered. Logging, monitoring, and human-review steps then become subscribers rather than special cases woven into the agent loop.

An AI agent integration ingests webhooks from a third-party service. Each webhook delivery is an event. The handler can’t pretend events arrive once and in order: the same InvoicePaid can be redelivered after a network blip, two LineItemAdded events can arrive out of order, and an occasional event can simply be lost when the upstream service crashes mid-send. The handler has to be idempotent (recognize a redelivered event by its ID), tolerant of reordering (use the event’s timestamp or sequence number rather than wall-clock arrival), and reconciled against the upstream’s authoritative state periodically (so lost events don’t accumulate into invisible divergence). None of this is exotic. It is what “event-driven, on a transport that guarantees at-least-once delivery without ordering” actually means in production.

A senior engineer reviewing a colleague’s pull request notices that the colleague has published an event named ChargeCustomer. The reviewer doesn’t redline the implementation; it works. They redline the name: “this is a command, not an event — past tense for facts, imperative for requests.” The correction is editorial but it matters because the next engineer to extend the system will read the past-tense convention as the contract. An event-shaped system whose names lie about which shape they are loses the property it was built for within a few releases.

Consequences

Treating “event” as a precise, named concept changes how you design and how you brief an AI agent, and it isn’t free.

Benefits. The vocabulary lets you make the trade explicit. Event versus command becomes a design decision rather than an accident of naming. Notification versus state transfer versus sourcing versus CQRS becomes a choice you can argue for or against rather than four overlapping defaults. Adding behavior costs almost nothing — a new subscriber is a new component — and audit, replay, and observability come for free because the event log already exists. Briefing an agent becomes easier because the right unit of work (“write a handler for the PaymentSettled event with idempotent semantics”) is sharper than a request shaped as “make the payment work.”

Liabilities. The benefits come with a tax. Tracing cause and effect is genuinely harder; the explicit call chain that sequential code provides is replaced by an implicit graph of publishers and subscribers, and the graph can only be reconstructed from the running system or from instrumentation deliberately added for the purpose. Event ordering, duplication, and loss are concerns that simply don’t exist in single-process function calls but do exist as soon as anything crosses a process boundary. Poorly designed event systems produce chains nobody can follow — A triggers B triggers C triggers D, and the bug is somewhere in the implicit pipeline. And the vocabulary is treacherous: “event” gets reused for in-process callbacks, distributed pub/sub, durable logs, and reactive streams, and each of those carries different delivery, ordering, and lifetime semantics. Carrying the concept precisely (which kind of event, which delivery guarantee, which shape) is the discipline that turns the cost back into a benefit.

The practical upshot is the same as for Concurrency: events are a property worth naming explicitly the moment they show up in a design conversation. The cost of leaving the concept implicit (a service quietly miswired as a command handler, a redelivery silently double-charging a customer, an agent loop whose progress can’t be observed because it isn’t event-shaped) is high enough that the discipline of naming pays for itself well inside a single project.

Sources

  • Event-driven thinking emerged from the structured-design community in the late 1980s and was elaborated across the 1990s and 2000s by many practitioners; no single author owns the idea, which is why this entry treats it as foundational vocabulary of the field.
  • Gregor Hohpe and Bobby Woolf catalogued the messaging vocabulary used here — publish/subscribe, message brokers, idempotent receivers, ordering and delivery guarantees — in Enterprise Integration Patterns (Addison-Wesley, 2003), the standard reference for event-flow design.
  • Martin Fowler’s 2017 article “What do you mean by ‘Event-Driven’?” untangled the four distinct senses people pack into the phrase (event notification, event-carried state transfer, event sourcing, CQRS), and the past-tense-event versus imperative-command distinction traces to that body of work.
  • Greg Young coined CQRS and developed event sourcing as we know it today, working in algorithmic-trading systems where an immutable, auditable event log was a regulatory necessity; his 2014 talk CQRS and Event Sourcing (Code on the Beach) remains the seminal explanation of both ideas.