Event notification vs state transfer

3 min read
On trails:EDAPATstep 1/7

Martin Fowler splits “event-driven” into four styles: event notification, event-carried state transfer, event sourcingEvent sourcingStore the events that happened, and work out the current state from them.Opens in a new tab and CQRSCQRSSeparate models for changing data and for reading it.Opens in a new tab. The last two have their own notes. The first two are the same event with a different amount of data in it. In event notification the event is thin: it says that something happened, often with little more than an id. In event-carried state transfer the event is fat: it carries the data a consumer needs, so the consumer does not have to ask the source for it.

In code

The same OrderPlaced event, both ways. The thin one names the order. The fat one carries the customer, the lines and the total.

events.csC#
// Thin: says what happened, the consumer fetches the details.namespace Thin{    public record OrderPlaced(Guid OrderId, DateTimeOffset OccurredOn);} // Fat: carries the data a consumer needs.namespace Fat{    public record OrderLine(string Sku, int Quantity, decimal UnitPrice);     public record OrderPlaced(        Guid OrderId,        Guid CustomerId,        IReadOnlyList<OrderLine> Lines,        decimal Total,        DateTimeOffset OccurredOn);}

What each one costs

With a thin event, a consumer such as Notifications receives OrderPlaced and then calls Orders back to read the order. That call happens later, so it returns the data as it is now, not as it was when the event was raised. It also means Orders must be reachable, and every consumer adds load to it. A fat event removes the call. Notifications and Shipping can work while Orders is down, because the data came with the message.

AspectThin (notification)Fat (state transfer)
Message sizeSmall, usually idsLarger, grows with the data
Source must be upYes, to answer the callbackNo, the data is in the event
Load on the sourceEvery consumer calls backNone after publishing
Data the consumer seesCurrent at the time of the callAs it was when the event was raised
Changing the contractSmall ripple, few fieldsEvery consumer of the fields is affected
Where the data livesOne ownerA copy in each consumer
Table 1. Thin and fat events

Stale copies

A fat event leaves a copy of the data in every consumer. Each copy is only as fresh as the last event that consumer processed. If the customer changes their address and the consumer has not yet handled that event, it holds the old one. Events that arrive late or out of order can leave a copy older than it should be (see message orderingMessage orderingOrder exists only within one key, and only while one consumer handles that key at a time.Opens in a new tab), and a copy is a second place where the data can be wrong. This is 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 a consumer that uses the copy has to accept it.

Fowler notes that state transfer gives greater resilience and lower latency, at the cost of more data moving around and more copies. Particular Software argues the other way: a large event ties every consumer to the producer’s data shape, and a new requirement then changes a contract many services depend on. Microsoft’s guidance is to choose per consumer, and it is fine to mix both in one system.

Choosing

Use a thin event when consumers need current data and the source is reliably available, or when the data is sensitive and few consumers should hold it. Use a fat event when a consumer must keep working while the source is down, or when it reads the same data often. Whatever is in the event is part of its contract, so changes need care (see event versioningEvent versioningChange an event's shape by adding to it, and run a second version beside the first when you must break it.Opens in a new tab). If a consumer needs data from another service that no event carries, see asking another service for dataAsking another service for dataFour ways to get data a service does not own, and what each costs.Opens in a new tab.


Sources