Guide

Many teams say "event-driven" when they mean different things. The starting point is the difference between a command and an event. The difference is about who depends on whom, and that decides how easy the system is to change.

Concept

A message is anything sent through a broker. A command asks one specific receiver to do something. An event states that something has already happened.

Commands and events

  • A command is imperative: ChargeCard, SendEmail. It has one intended handler and can be rejected. The sender cares about the outcome.
  • An event is past tense: OrderPlaced, PaymentFailed. It is an immutable fact. It can have zero or many subscribers, and it can't be rejected. You can't refuse a fact. The publisher doesn't know or care who listens.

The distinction is about coupling direction. With a command, the sender depends on the receiver: "I need Payments to charge this card." With an event, the receivers depend on the publisher: "I care when orders are placed." The publisher is unaware of them. Adding a fraud-check consumer to OrderPlaced requires no change to the Orders service at all.

Thin events and fat events

Events differ in how much data they carry.

A thin notification carries only an identifier:

JSON
{"type": "OrderPlaced", "orderId": 8812}

Consumers that need details call back to the Orders service. The source of truth stays in one place, and the event stays small. But every consumer now makes a synchronous call. Ten consumers mean ten extra reads per order, which brings back the runtime coupling you wanted to remove.

A fat event, or event-carried state transfer, carries what consumers need:

JSON
{"type": "OrderPlaced", "orderId": 8812, "customerId": 310,
 "items": [{"sku": "A1", "qty": 2}], "totalCents": 4900, "status": "paid"}

Consumers keep their own copy and never call back. The email service keeps working even when Orders is down. The cost is larger events, duplicated data, and consumers that see data that is only eventually consistent with the source.

Most production systems lean toward fat events for core business facts. A useful middle ground is to carry the full current state of the changed entity. That makes events self-healing: a consumer that missed an update gets the latest state with the next event.

Common mistakes

  • Naming commands as events. ChargeCard is a request. CardCharged is a fact. Keep the tense honest.
  • Thin events with a callback per consumer. You have rebuilt the synchronous coupling you were trying to escape.
  • Fat events with no owner. Someone must own the schema, or every consumer's assumptions become hidden coupling.

Check yourself

1. Why can a fraud-check consumer subscribe to OrderPlaced without changing the Orders service? Events don't name a receiver. The publisher doesn't know its subscribers, so new ones need no change to it.

2. Ten consumers receive a thin event. How many calls can return to the Orders service? Up to ten extra reads per order. That reintroduces runtime coupling and load on Orders.

Key takeaways

  • A command asks one receiver to act. An event announces a fact that anyone can use.
  • Events invert the dependency. Publishers don't know their consumers.
  • Thin events keep data central but cause callbacks. Fat events decouple availability and duplicate data.
  • Carry the full current state of the changed entity when you can.

Further learning

Martin Fowler's article untangles the four meanings people give to "event-driven". It is the best reading before designing one.

What do you mean by "Event-Driven"? (Martin Fowler)What do you mean by “Event-Driven”?martinfowler.com

Martin Fowler's GOTO talk covers the same ground in twenty minutes.

The Many Meanings of Event-Driven Architecture • Martin Fowler • GOTO 2017GOTO Conferences

Next, see how events are produced from an existing database in change data capture.