Event notification vs event-carried state transfer vs event sourcing as three genuinely different patterns with different coupling/payload/replay trade-offs, and the four practical mechanisms for implementing event-driven communication — brokers, webhooks, CDC, and the outbox pattern.
Published September 23, 2026
{ "eventType": "OrderShipped", "orderId": "ord_123" }
The event carries almost nothing — just enough to say SOMETHING happened and identify what. A consumer that cares about the details must make a SEPARATE call back to the source service to fetch them. This is the lightest-weight, most loosely-coupled pattern — but it reintroduces a synchronous dependency at consumption time (the callback), and if the source service is unavailable when the consumer processes the event, the consumer is stuck.
{ "eventType": "OrderShipped", "orderId": "ord_123", "trackingNumber": "1Z...", "carrier": "UPS", "shippedAt": "..." }
The event carries everything a typical consumer would need, avoiding the callback entirely — a consumer can process it fully self-contained, even if the source service is completely down at that moment. The cost: larger payloads, and a real coupling risk if the event's SCHEMA needs to change (every consumer depending on specific fields needs to handle that change, similar to API Contract Design's versioning concerns applied to event schemas instead of REST endpoints).
As covered concretely in OMS — State Machine Design, this goes further than either of the above — the event log isn't just a NOTIFICATION mechanism, it's the actual SOURCE OF TRUTH, with current state derived by replay. This is a fundamentally different architectural commitment than the other two patterns (which typically sit alongside a conventional database holding current state) and is usually reserved for domains where the full history genuinely matters, not applied as a default.
Coupling Payload Replay/Audit
Event notification: lowest smallest weakest (data isn't in the event)
Event-carried state: moderate larger moderate
Event sourcing: N/A (it IS the model) strongest, by design
The right choice for a given event isn't universal — it depends on how OFTEN consumers actually need the full data (favoring event-carried state transfer if nearly every consumer needs it, avoiding a callback on every single event) versus how volatile/large the data is (favoring event notification if the data changes fast or is large, where embedding a possibly-stale or bulky copy in every event is wasteful).
Message broker (Kafka/RabbitMQ) for pub-sub: the general-purpose mechanism — a publisher emits, any number of independent subscribers consume, covered in depth in Messaging Technology Choices next in this chapter.
Webhooks for cross-system notification: an HTTP callback registered by an external system, invoked when an event occurs — the standard mechanism for notifying a THIRD-PARTY system outside your own infrastructure (exactly the payment-processor webhook pattern from Payment — Failure Handling & Reconciliation), since you can't typically have an external party subscribe directly to your internal message broker.
Database change-data-capture (CDC): rather than the application explicitly publishing an event, a CDC tool watches the DATABASE's own write-ahead log/binlog and turns each row change into an event automatically — useful when you want event-driven reactions to data changes WITHOUT modifying application code to explicitly publish for every write path, though it couples consumers to the database's internal schema rather than a deliberately-designed event contract.
The outbox pattern: solving a genuine correctness problem — if a service both writes to its own database AND publishes an event as two SEPARATE operations, a crash between them means either the write succeeds with no event ever published, or (worse) inconsistent partial states. The outbox pattern writes the event to an "outbox" table IN THE SAME DATABASE TRANSACTION as the actual business write, then a separate, reliable process reads the outbox table and actually publishes to the message broker — this guarantees the event is published if AND ONLY IF the business write actually committed, without needing a distributed transaction spanning the database and the broker.
Q: Why is the outbox pattern needed at all — why not just publish the event, then write to the database? A: Either ordering has the same fundamental problem: whichever operation happens SECOND could fail after the first one already succeeded, leaving the two out of sync (a published event with no matching DB write, or a DB write with no published event) — the outbox pattern avoids this specifically by making the event-write part of the SAME atomic database transaction as the business write, so there's no window where one could succeed without the other.
Q: Can event notification and event-carried state transfer be mixed for the same event type? A: Yes, and it's a reasonable middle ground — an event can carry a SMALL amount of commonly-needed data (avoiding a callback for the common case) while still requiring a callback for consumers needing rarer, larger, or more volatile additional details, rather than forcing every consumer into the same all-or-nothing choice.
Q: How does CDC's coupling to the database schema become a real problem in practice? A: A schema migration (renaming a column, changing a table's structure) that wasn't designed with CDC consumers in mind can silently break every downstream consumer relying on the old column names/structure — this is a real, easy-to-overlook coupling risk CDC introduces that a deliberately-designed event schema (event-carried state transfer) doesn't have, since the event contract is explicitly versioned and decoupled from internal database structure.
Q: Does the outbox pattern's separate publishing process introduce its own reliability concerns? A: Yes — that process itself needs to be resilient (it's typically a polling job or a CDC-based reader watching the outbox table specifically) and idempotent on the PUBLISHING side too (if it crashes after publishing but before marking the outbox row processed, it might publish again on restart) — this is the same at-least-once-plus-idempotent-consumer reasoning from Message Queue System, applied specifically to the outbox-to-broker publishing step.