"We're event-driven" can mean three genuinely different architectures, each with a different answer to one concrete question: when a consumer receives an event, does it already have everything it needs, or does it have to go ask for more?
3 min read
{ "event": "OrderUpdated", "orderId": "abc123" }An event notification carries almost no data — just enough to say that something happened and what it concerns. A consumer receiving this has to make a separate call back to the source service ("give me order abc123's current details") to get anything useful. This keeps events tiny and keeps a single service as the sole authority on that data's actual current state — but it creates a real, synchronous dependency: the whole point of decoupling with events erodes if every consumer immediately turns around and calls the producer back over HTTP anyway.
{
"event": "OrderUpdated",
"orderId": "abc123",
"status": "shipped",
"items": [{ "sku": "WIDGET-1", "qty": 2 }],
"total": 49.98
}Event-carried state transfer includes everything a consumer needs directly in the event payload — no callback required. A shipping-notification service can send the confirmation email using only what's in the event, never touching the order service at all. This restores real decoupling (the order service can go down entirely and every consumer still has what it needs from events already received) at the cost of larger messages and, more subtly, the need to think carefully about event schema (lesson 10) since consumers now depend on the shape of the data itself, not just the fact that something happened.
Both patterns above still treat a database row as the real state, with events as a side effect of changing it. Event sourcing inverts this: instead of storing "order #123's current status is shipped," the system stores the full sequence of events that happened to it — Created, PaymentReceived, Shipped — and the current state is whatever you get by replaying every event for that entity from the beginning.
Traditional: orders table row → status = "shipped" (the history is gone)
Event sourcing: OrderCreated, PaymentReceived, Shipped (the history IS the data)
This gives a genuine, complete audit trail for free (every past state is derivable, not just the current one) and lets new consumers be added later that derive entirely new views from history that already happened — but it's a real architectural commitment: querying "what's this order's current status" now means replaying its event stream (or maintaining a separately-updated read model, which is exactly what CQRS in the next lesson is for), not a simple row lookup.
A real system commonly mixes all three: event-carried state transfer for most cross-service communication, full event sourcing reserved for specific entities where the complete history genuinely matters (a financial ledger, an order's full lifecycle for dispute resolution), and plain notification for cases where a callback is cheap and the data is rarely needed. Defaulting straight to event sourcing everywhere, because it sounds like the most "correct" pattern, adds real replay/read-model complexity to entities that never needed a history at all.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What does an event-notification-style event typically contain?
2. What's the main trade-off event-carried state transfer accepts in exchange for real decoupling?
3. In event sourcing, what is treated as the actual source of truth?
Message Queues & Event-Driven Architecture