Event Notification
"Something just happened — come fetch if you care." The leanest possible event, in exchange for residual synchronous coupling.
Problem
A system publishes changes but does not want to pollute the broker with bulky payloads. Not every consumer cares about every order; some only want to filter and enrich on demand. How to notify without paying the cost of a full snapshot?
Forces
- Full events weigh KBs or even MBs; Notifications weigh a few hundred bytes.
- The synchronous consumer ↔ producer coupling re-emerges on the detail fetch.
- The consumer chooses when to enrich (lazy) rather than storing full state.
- No self-contained replay: without details, the past cannot be reconstructed.
- The producer must expose a stable, versioned API for the fetch.
Solution
The event carries a minimum: { id, type, aggregateId, version, occurredAt, source }. The interested consumer decides whether to call GET /orders/{aggregateId} on the producer to fetch state. Size stays constant regardless of entity, enabling fan-out at very large scale. Fowler stresses: Event Notification fits when the consumer doesn't consume everything, and accepts added latency (an extra round-trip).
Structure
Producer
│ publish (minimal payload, ~200 B)
▼
┌────────────────────────────────────────────┐
│ Event: OrderConfirmed │
│ id: order-42, version: 7 │
│ occurredAt: 2026-05-16T10:42:00Z │
│ source: order-service │
└──────────┬─────────────────────────────────┘
│ Consumer X picks it
▼
Consumer X
│ GET /orders/42 (HTTP, sync)
▼
Producer API (enrich on demand) EDI implementation
An EDI hub publishes InvoiceIssued with only the ID, partner and UBL number. A "payment broker" consumer fetches the details only for the subset that flows through it. Conversely, a PEPPOL CTC fiscal consumer needing the full UBL payload is better served by Event-Carried State Transfer. Typical case: MdnReceived with the AS2 ID — an ops supervisor fetches the signed payload for audit only when the MDN reports failure.
Anti-patterns
- Notification followed by an uncached fetch — the producer absorbs N calls per published event.
- No idempotency on the GET — a consume retry duplicates the enrichment and the invoice.
- Notification without
version— a consumer cannot detect a newer state has already been fetched. - Producer without circuit breaker — massive fan-out brings the enrichment API down.
- Unversioned fetch API — a breaking change breaks every consumer.
Related patterns
- Event-Carried State Transfer — full alternative when downstream needs everything.
- Event Message — generic message style.
- Content Enricher — the consumer-side step performing the fetch.
- Circuit Breaker — protect the enrichment API.
Sources
- Fowler M. — What do you mean by "Event-Driven"?, 2017. martinfowler.com/articles/201701-event-driven.html
- Newman S. — Building Microservices, 2nd ed., O'Reilly 2021, ch. 4.
- Stopford B. — Designing Event-Driven Systems, O'Reilly 2018, ch. 6 "Event Notification".