Why event-driven

2 min read
On trails:EDAstep 1/8

In an event-driven design, a service announces what happened and does not call the services that care. The services that care listen for the announcement and react on their own.

The checkout problem

In a small online shop, a customer presses “Place order”. Orders must charge the card, reserve stock and send a confirmation. The direct way is for Orders to call Payments, then Inventory, then Notifications, one after another, inside the same request. That has three problems.

  • It is slow. The customer waits for the sum of every call, including the ones that do not affect whether the order succeeded, such as the confirmation email.
  • It is fragile. If Notifications is down, the checkout fails, or Orders needs code to survive that.
  • It is coupled. Orders knows every service that needs to hear about an order. Adding a Loyalty service means changing and redeploying Orders.
Checkout before and after. Before, Orders calls Payments, Inventory and Notifications directly and waits for all three. After, Orders publishes one OrderPlaced event to a broker, and the three services each receive it. BEFORE: ORDERS CALLS EACH SERVICE AND WAITS Orders Payments Inventory Notifications the customer waits for all three calls, and a failure in any of them fails the checkout AFTER: ORDERS ANNOUNCES ONE EVENT Orders Broker OrderPlaced Payments Inventory Notifications Orders publishes one event and returns. A new service subscribes without changing it.
The same checkout, drawn as calls and drawn as one announcement.

Announce, don’t call

After the change, Orders saves the order and publishes one event, OrderPlaced, then returns to the customer. Payments, Inventory and Notifications each receive it and do their own work. Orders does not know who is listening, and a service that is down receives the event when it comes back, because the broker holds it. Microsoft’s architecture guide describes the same property: a producer does not know which consumers are listening, and new consumers can be added without changing producers or other consumers.

The four roles

  • Event. A record that something happened, such as OrderPlaced. See events, commands and queriesEvents, commands and queriesThree kinds of message: an event states a fact, a command asks one owner to act, a query only reads.Opens in a new tab.
  • Producer. The service that publishes the event. Orders, here.
  • Consumer. A service that receives the event and acts on it.
  • Broker. The middle piece that accepts events and delivers them. See broker vs event streamBroker vs event streamA broker hands messages out and removes them once acknowledged, a stream keeps a log that consumers read at their own pace.Opens in a new tab.

Async is not the same as event-driven

A call can be asynchronousAsync and awaitAwait frees the thread while an operation waits, and the method picks up where it left off.Opens in a new tab and still tightly coupled. If Orders puts a ChargeCard message on a queue that only Payments reads, nothing blocks, but Orders still names the service it depends on and expects it to act. What makes a design event-driven is that the producer does not know its consumers. Whether the caller waits is a separate question.

The catch

The customer can no longer be told the final result in the same response. When the response returns, Payments may not have run yet, so the order is pending until PaymentReceived arrives. Data in different services is briefly out of step (see eventual consistencyEventual consistencyAfter a change, other services catch up later, and the design has to allow for the gap.Opens in a new tab), and following one checkout across several services is harder than reading one call stack. When to use event-driven architectureWhen to use event-driven designWhere events help, where a plain call is better, and why most systems mix both.Opens in a new tab covers when that trade is not worth making.


Sources