skip to content

questions

6

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)?

level: juniorimportance: must knowfreq 70%

answer

  1. thin vs fat event
  2. callback vs self-contained payload
  3. thundering herd on callback
  4. staleness on carried state
  5. Fowler's event styles

basics

~10 s

It'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 s

This 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

for a junior

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.

for a middle

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.

for a senior

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.

for a principal

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

context

open as a page

A team designing an event-driven checkout flow is choosing between publishing thin 'OrderPlaced' notifications that consumers must query back for details, versus publishing events that carry the full order state. What are the concrete coupling and data-freshness trade-offs between these two approaches?

level: middleimportance: must knowfreq 75%

basics

~20 s

Thin events keep the producer as the single source of truth so data is always current, but every consumer has to call back, tying them to the producer being online. Fat events remove that call-back dependency but consumers can end up working with slightly outdated data.

open as a page

You're designing the integration between an order service and five downstream consumers (inventory, shipping, analytics, fraud, and notifications), each needing different amounts of order detail. Walk through how you would decide, per consumer, between event notification and event-carried state transfer, and when you'd introduce event sourcing instead of either.

level: seniorimportance: must knowfreq 65%

basics

~20 s

Pick the style per consumer based on how much data they need, how current it must be, and how many consumers are asking - big or many-and-varied needs favor a fat event, few-and-current needs favor callbacks. Event sourcing is a different, bigger decision: making events the actual system of record, not just a way to tell other services something happened.

open as a page

A customer-profile service publishes event-carried state transfer messages ('CustomerUpdated') that include the customer's full current profile in each payload, delivered over a message broker that does not guarantee ordering across partitions. A downstream analytics service applies each event's payload directly onto its local copy as soon as it arrives. What can go wrong, and what technique fixes it?

level: middleimportance: should knowfreq 55%

basics

~20 s

If an older update arrives after a newer one, the analytics copy can be overwritten with stale data. Adding a version number or timestamp to each event and only applying it if it's newer fixes this.

open as a page

A team has been running an event-carried state transfer integration in production for two years. Consumers have drifted out of sync with producers a few times due to missed events, and the event schema has changed twice, breaking older consumers that hadn't upgraded. What concrete techniques would you put in place to make this integration more resilient to both staleness and schema drift going forward?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Add version numbers so consumers can tell old data from new, periodically resend full snapshots so gaps self-heal, and evolve the event schema by adding fields instead of breaking old ones. Together these catch and fix drift instead of letting it accumulate silently.

open as a page

A platform team wants to give internal consumers the low-callback benefits of event-carried state transfer for order events, but the order aggregate is large (megabytes, with attachments) and some fields are sensitive (PII, payment tokens) that shouldn't be broadcast to every subscriber. Design an approach that gets most of the coupling and freshness benefits of carried-state events without shipping the entire aggregate to everyone, and explain how it relates to event sourcing if the order service internally uses an event-sourced write model.

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Send a medium-sized event with the commonly needed fields plus a reference (like a claim check) that lets consumers who truly need the big or sensitive parts fetch them separately, rather than sending everything to everyone. Event sourcing, if used internally, is a separate decision about how the order service stores its own history - it doesn't have to leak into what gets published externally.

open as a page