A shipping service publishes a message saying only 'Order 4521 was placed', with no order details attached, and any subscriber that needs more information must call back to the order service's API to fetch it. What is this messaging style called, and how does it differ from a style where the message itself carries the full order payload (items, price, address)?
answer
- thin vs fat event
- callback vs self-contained payload
- thundering herd on callback
- staleness on carried state
- Fowler's event styles
basics
~10 sIt's called event notification: a thin 'something happened' alert with no data, so listeners must ask for details afterward. The other style, event-carried state transfer, puts all the needed details inside the message itself.
solid answer
~40 sThis is event notification: a small message that just announces something happened, like 'OrderPlaced, id=4521', without the actual order data. Consumers that need details must call back to the order service's API to fetch them. Event-carried state transfer is the alternative: the event payload itself contains the relevant state (items, price, address), so consumers can act on it without an extra call. Event notification keeps messages tiny and consumers always get freshly queried data, but it creates a runtime dependency on the source service's availability and adds chatty callback traffic. Event-carried state transfer removes that coupling and lets consumers work offline or asynchronously, at the cost of larger, more duplicated payloads that can go stale between publish and consumption.
go deeper
Should recognize the two styles by description and state the core difference: thin alert plus callback versus self-contained payload, without needing to name Fowler or discuss failure modes in depth.
Should articulate the coupling trade-off (temporal/runtime coupling from callbacks vs schema coupling from embedded payloads) and give one concrete downside of each style.
Should reason about when to pick each style for a given system (consumer count, payload size, freshness needs) and describe at least one production failure mode per style, such as callback storms or silent staleness.
Should discuss this as an architectural decision affecting schema ownership and evolution across an organization, including how mixing styles per event type or introducing versioned schemas manages the trade-off at scale.
## What actually travels on the wire Martin Fowler distinguishes several ways an event-driven system can move information between a producer and its subscribers, and the two most commonly contrasted styles are **event notification** and **event-carried state transfer**. Understanding the difference starts with what actually travels on the wire. - **Event notification** — the message is deliberately thin: it names what happened and carries just enough identifying information -- typically an entity ID and maybe a type -- for a subscriber to decide whether it cares. An `OrderPlaced` event might contain nothing but `{orderId: 4521, occurredAt: ...}`. If a subscriber, say an inventory service, needs to know which items were ordered, it must make a separate synchronous call back to the order service's API (or query its database/read model) to retrieve that data. - **Event-carried state transfer** — the producer instead embeds the state a subscriber is likely to need directly in the event body -- order items, prices, shipping address -- so the event is self-sufficient and no callback is required. ## Why both styles exist The reason both styles exist is that they solve different problems. 1. **Event notification** exists to decouple 'who needs to react' from 'what exactly happened', while keeping the source of truth for data in one place: the producer's own store, queried on demand. This is attractive when the data is large, sensitive, or changes rapidly, because publishing it repeatedly would be wasteful or risky, and because a single authoritative read path avoids subscribers holding divergent copies. 2. **Event-carried state transfer** exists to eliminate the runtime dependency that callbacks create: once a consumer has the event, it can complete its work entirely from local data, without caring whether the producer's service is currently up, fast, or reachable. This is exactly the pattern behind CQRS read-model projections and many 'materialized view via events' designs, where a service built the local copy specifically so it never has to call anyone back. ## The trade-off, named explicitly The trade-offs are symmetric and worth naming explicitly. - **Event notification** keeps messages small and avoids duplicating data across the system, and consumers always see the current state at the moment they ask (freshness). But it reintroduces temporal coupling: the consumer's processing pipeline now depends on the producer's API being available and responsive at consumption time, which is exactly the kind of runtime coupling asynchronous messaging was supposed to remove. It also tends to create a 'thundering herd' pattern -- if a batch of 10,000 `OrderPlaced` notifications lands, every consumer independently calls back to the order service to fetch details, multiplying load on the producer far beyond what a single event stream implies. - **Event-carried state transfer** avoids that callback storm and lets consumers scale and fail independently of the producer, but it pays for that in payload size, schema surface area, and staleness risk: the data in the event is a snapshot as of publish time, and if the order is later modified, subscribers holding the old event have outdated state until a new event arrives. It also increases coupling of a different kind -- schema coupling -- because now every consumer depends on the shape of the producer's payload, and the producer has to think carefully about what state it 'owns' enough to broadcast versus what should stay private. ## Failure modes in production Failure modes show up differently for each style in production. - **With event notification**, an outage or slow response from the producer's callback API turns an otherwise resilient async pipeline into a synchronous failure chain -- consumers block, retry, or drop events, and a producer incident cascades into every subscriber. - **With event-carried state transfer**, the classic failure is silent staleness: a consumer processes an event, caches the embedded state, and never learns about a subsequent update if the producer's publishing logic misses an edge case (e.g., a manual DB fix that bypasses the event-publishing code path), leaving the consumer permanently out of sync with no error to alert on. - **Payload bloat** is a slower-burning failure mode: as more consumers ask 'can you also include X in the event', producers grow ever-fatter payloads that duplicate large chunks of their domain model, blurring ownership and making schema evolution painful. ## Where it shows up A concrete real-world illustration: an e-commerce platform's inventory service used to subscribe to a thin `OrderPlaced` notification and call back to the order service for line items -- this worked fine at low volume, but during flash sales the order service's API became a bottleneck as thousands of inventory-service instances hammered it simultaneously for details. Switching `OrderPlaced` to carry the full line-item list (an event-carried state transfer) removed that callback storm entirely, at the cost of the inventory service needing to handle occasional stale-price scenarios when a price-correction event arrived after the original order event.
- If the order service that publishes these events goes down, which style keeps subscribers working, and why?Event-carried state transfer keeps subscribers working because they already hold the data they need locally and never call back to the producer at consumption time. Event notification breaks down because subscribers must reach the order service's API to get details, so its outage blocks or fails every downstream reaction.
- What happens to message size and schema ownership as more consumers use event-carried state transfer?Payloads tend to grow as different consumers each ask for one more field to avoid a callback, and the producer ends up broadcasting more of its internal domain model than it might want to. This blurs ownership boundaries and makes the event schema harder to evolve without breaking someone.
- Could a service mix both styles for different events?Yes - it's common to use event-carried state transfer for small, frequently needed fields and event notification for large or sensitive data that few consumers need, calling back only when necessary. The decision is made per event type based on payload size, consumer count, and freshness requirements.
Event notification is like a text that just says 'call me' - you still have to phone back for the actual news; event-carried state transfer is like a text that includes the full story upfront, so you never need to call back (but it might be old news by the time you read it).
saying these in an interview costs you the question
- Says the two styles are basically interchangeable with no trade-off
- Doesn't mention that event notification requires a callback to the producer
- Claims event-carried state transfer payloads are always small
- Thinks staleness only affects event notification, never carried-state payloads
- Can't explain why callback-heavy notification patterns can overload the producer