Event Types — Notification, Event-Carried State Transfer & Event Sourcing
Notification, event-carried state transfer, and event sourcing are different contracts. Payload richness decides coupling, races, and whether the log is the source of truth.
- 1Gist
- 2Maps
- 3Q&A
- 4Sandbox
Voice readout needs Web Speech Synthesis in this browser.
Question ladder
L1
What does a notification consumer do that an event-carried-state consumer does not?
Answer
It fetches current state from the write model. The fat event already carries the fields it needs.
L2
Why is a notification dangerous on a high-churn order?
Answer
The GET after delivery often returns a newer state than the event meant, such as CANCELLED after OrderPlaced.
L3
Is event-carried state the same as copying the database onto the bus?
Answer
No. Publish the business facts consumers need. A raw row dump is a CDC choice, and it couples consumers to the table.
L4
What is the source of truth in event sourcing?
Answer
The append-only stream for that aggregate. Current state is a fold. Snapshots are an optimization.
L5
Does CQRS require event sourcing?
Answer
No. CQRS splits the write model from the query model. Events, CDC, or a projection job can feed the read side.
L6
Where should PII sit if you use fat events?
Answer
Minimize it. Tokenize or encrypt fields, or publish a reference when compliance wants the rich record to stay on the write model.
L7
How does CDC relate to these three styles?
Answer
CDC can source events from the commit log. You still choose whether consumers see a thin change, a mapped integration event, or a folded domain stream.
Failure modes
Notification race
The consumer fetches after the entity has already moved on, so it applies a newer state than the signal meant.
Sensitive fat events
Event-carried state copies fields onto the bus that every subscriber can read.
Event sourcing on a CRUD table
Snapshots, upcasters, and rebuilds show up for a record that never needed history as truth.
Misconceptions
CQRS requires event sourcing.
CQRS is a command and query split. Event sourcing is optional input to the read model.
Event-carried state means publish the whole row.
Publish the business facts the consumer needs. Table mirrors belong to CDC, with their own schema.
Interviewer traps
Treating every event as a vague message.
Name the payload style, who owns the rich state, and how versions move.
Rebuilding Avro compatibility tables in this answer.
Say integration events need a compatibility plan, then point at the Kafka lessons for the registry.
Same OrderPlaced, three contracts
Prefer
Event-carried state for the integration boundary
The event carries the fields a consumer needs at that version. Search and analytics apply it without calling the write model back.
- Consumers stay up when the write API is busy.
- The payload is a business fact, not a table dump.
- A version lets a projection ignore stale copies.
Alternative
A thin notification for every consumer
The bus stays small, and every consumer fetches. On a hot order the fetch often observes a later state than the event intended.
- The write model becomes a read hotspot.
- Cancel-after-place races are easy to miss.
- Fine when the write model must remain the only rich API.
Pick a style before you pick a broker
The broker is transport. The contract is the payload.
- 1
Name who needs the rich state
If many teams must read the latest row, a notification plus a fetch keeps one source of truth. - 2
Name who must act offline
If search or billing can work from the fact alone, put those fields in the event. - 3
Name whether history is the record
Event sourcing fits an aggregate you rebuild, audit, or time-travel. It is a poor default for a CRUD table. - 4
Publish with a stable id
Whatever the shape, the outbox or CDC path emits it. This page does not invent a second publisher.
Overview
Not every event is the same shape. Senior interviews expect three styles:
- Notification — an id, a type, maybe a version. The consumer fetches.
- Event-carried state transfer — enough fields to act without a fetch.
- Event sourcing — the ordered log is the source of truth. Current state is a fold.
Pick payload richness, coupling, and replay on purpose. Then publish through the outbox or CDC. Do not redesign the broker on this page.
Three styles
| Style | Payload | Consumer | Strength | Cost | Prefer when |
|---|---|---|---|---|---|
| Notification | Id and type, maybe a version | Fetch current state | Small messages. Rich state stays on the write model | Extra fetches. A race if state already moved | Many consumers need the latest view, and the write model is the API |
| Event-carried state | Fields required to act | Apply the event | Decoupled. Works for projections | Duplication. Sensitive data on the bus. Heavier schema | Downstream can use the snapshot-at-that-version |
| Event sourcing | Full ordered facts for one aggregate | Fold the stream | Audit, rebuild, time travel | Snapshots, upcasters, operational skill | The aggregate truly needs history as truth |
Rule of thumb: event-carried state for integration events across teams. Notification when the write model must remain the only rich API. Event sourcing only for aggregates that benefit from the log as the record.
How the three flows differ
Flow
- 1
1. Notification carries orderId
- next2. Consumer fetches the order
- 2
2. Consumer fetches the order
- 3
3. Fat event carries lines and totals
- next4. Consumer projects locally
- 4
4. Consumer projects locally
- 5
5. OrderCreated
- next6. ItemAdded
- 6
6. ItemAdded
- next7. OrderSubmitted
- 7
7. OrderSubmitted
- next8. Fold into current Order state
- 8
8. Fold into current Order state
Lesson map
Notification, fat event, or a fold
The cancel is already committed. A notification with only the id is still in flight. The consumer has not fetched.
Architecture. Write model CANCELLED. Broker Notify in flight. Consumer Fetching
Select a node to see why it exists, or an edge to see the protocol, direction, effect, and consequence.
Mermaid export
flowchart TB write["Write model CANCELLED"] broker["Broker Notify in flight"] consumer["Consumer Fetching"] write -->|Notify| broker consumer -->|Fetch| write write -->|Fat event| broker broker -->|Fold| consumer broker -->|Stale apply| consumer
The first pair is a doorbell plus a lookup. The second pair is a letter. The last chain is a diary. They can coexist: an aggregate may be event-sourced inside one service and still publish an event-carried integration event to other teams.
Notification race versus a versioned fat event
Sequence
- 1
Write model → Broker
1. Notify OrderPlaced for id 42
- 2
Write model
2. The user cancels order 42
- 3
Broker → Consumer
3. Deliver the notification
- 4
Consumer → Write model
4. Fetch order 42
- 5
Write model → Consumer
5. CANCELLED, newer than the signal
- 6
Write model → Broker
6. Fat OrderPlaced at version 3 with status PLACED
- 7
Broker → Consumer
7. Deliver the fat event
- 8
Consumer
8. Upsert only if the event version is greater
The failure path is steps 1 through 5. The consumer wanted "placed" and observed "cancelled". A version on a fat event does not remove races inside the write model. It stops an older fact from overwriting a newer projection.
Domain events and integration events
Domain events stay inside a bounded context. They can be fine-grained. They often feed event sourcing or local policies.
Integration events cross teams. They are versioned, usually event-carried state, and they need an explicit compatibility story: consumers tolerate unknown fields, and producers add fields before they remove them. Broker schema registries and poison routing belong to Apache Kafka — Topics, Partitions, Brokers & Consumer Groups and delivery semantics. This page does not re-teach Avro or Protobuf matrices.
Event sourcing, only as far as the interview needs
- Append-only stream per aggregate id.
- A command validates against the folded state, then appends new events.
- A snapshot every N events speeds reload. It is not the source of truth.
- Projections build query models. That split is the CQRS lesson.
- An upcaster translates an old event version when it is read.
CQRS does not require this. The write model can be an ordinary row. The read model can still be a projection.
Fold a stream (run this)
Four events become one order. to_ecst is the integration-shaped snapshot you might publish after the fold. It is not a second source of truth.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Version guard on a notification (run this)
The write model is already at version 5 and cancelled. A notification that still says version 3 must not refresh the projection from that stale signal.
Press Run. Snippets must be self-contained — no network, files, or native modules.
Cheat sheet
Notification. Small bus traffic and one rich API. Chatty reads, races, and load on the write model.
Event-carried state. Autonomous consumers and a natural fit for search or analytics. Larger messages, sensitive fields, and schema discipline.
Event sourcing. Audit, rebuild, and temporal queries. A skill tax, snapshot and upcast operations, and a bad fit for every table.
CDC can source any of these from the commit log. Change Data Capture owns log versus poll. You still choose whether consumers see a thin change, a mapped integration event, or a folded stream. Transactional Outbox & Inbox Patterns owns the case where the public event is a domain fact, not the raw row.
Interview Q&A
What is the fastest way to fail this design question?
Answer
Treat every event as a vague message with no payload strategy and no version.
When is a notification dangerous?
Answer
High-churn entities, where the fetch after the signal returns a newer state than the event meant. Cancel after place is the usual story.
Is event-carried state just a database copy on the bus?
Answer
No. Publish the business facts consumers need. A raw row dump is a deliberate CDC contract, with table coupling and tombstones, taught on the CDC hub.
How does CDC relate?
Answer
CDC reads the commit stream. The envelope might look like a notification or a Debezium row image. Mapping that image into a stable integration event is still a product decision.
Do you put personal data on the bus?
Answer
Minimize it. Tokenize or encrypt fields. Prefer a reference when compliance says the rich record stays on the write model.
What do snapshots change?
Answer
Reload speed. The stream remains the source of truth. A snapshot that cannot be rebuilt from events is a second write model you did not mean to create.
Domain event or integration event?
Answer
Inside one context, fine-grained domain events are normal. Across teams, publish a small set of versioned integration events and keep the internal stream private.
Where does the outbox sit?
Answer
After you choose the contract. The same local transaction records the business change and the integration event. Relay mechanics stay on Transactional Outbox & Inbox Patterns.
Pitfalls
Write the JSON for a notification, a fat integration event, and the four domain events you would fold. For each, say who is allowed to be down, and which consumer race is still possible.