Event Driven Architecture: Pub/Sub, Event Sourcing, and Choreography
Event driven architecture: the pub/sub vs message queue comparison, event sourcing as the system of record, and choreography vs orchestration, how services coordinate without a conductor.
This article is the architecture cluster’s third stop, and it arrives with debts to pay. The hub, microservices architecture, weighed whether a fleet of services should be a set of direct calls at all; the message queues article then defined the worklist: one queue, one consumer per message, delivery guarantees priced and anchored. Its FAQ pointed here for the other half: the broadcast model, where every subscriber hears the event, and the architectural style built on it. This is that comparison, followed by the two design decisions that decide whether an event-driven system is honest: where the record of truth lives, and how the flow stays findable.
The vocabulary deserves precision up front. An event is a fact: something that already happened, stated in the past tense, addressed to nobody in particular. A command is an instruction, addressed to a specific handler; a message is the envelope either can travel in. The distributed transactions article put event-driven patterns on the shortlist before two-phase commit for exactly this reason: a fact published once can trigger as many reactions as the system needs, without the sender waiting for any of them.
The style is not a free upgrade, and this article will not pretend otherwise. Asynchronous is not automatically better; it trades latency you can measure for flow logic you must reconstruct, and it couples services through schemas instead of calls. In a distributed system that is still coupling, just of a quieter kind, and pretending otherwise is how event-driven systems rot. What follows defines the pattern, pays the comparison debt in full, then prices the two patterns that keep the style honest.
What is event driven architecture
Event driven architecture is a style in which services communicate by publishing events (immutable, past-tense notifications that something happened) and by subscribing to the events they care about, rather than calling each other directly and waiting for answers.
The cast reuses the queue’s roles with one decisive change. A producer publishes an event to a topic; the broker stores it and fans it out; a subscriber receives every event on the topics it subscribed to. The change is in the last role: where a queue hands each message to one consumer, an event goes to every subscriber, and the publisher does not know who they are. That ignorance is the point. The publisher states a fact; the system decides who reacts. Coupling drops from “the sender must know the receiver” to “the sender must know the fact”, a decoupling in shape and in time that the message queues article priced for queues and that fan-out extends to the whole fleet.
The events themselves carry a discipline. They are named as facts (OrderPlaced, not CreateOrder) because a command presumes its consumer and a fact presumes nothing. They are published, not sent: nobody is on the other end of a publish the way a caller is on the other end of a request. And they are immutable once written, which is what makes them safe to fan out, safe to store, and (when the style is taken to its conclusion) safe to treat as the record of what the system actually did. That conclusion has a name, and it gets a section of its own.
Pub/sub vs message queue
The queue article answered this in one line and pointed here, so here is the full answer. A message queue is a worklist: each message is handed to one consumer, competing workers drain the backlog, and the work happens once. Pub/sub is a broadcast: each event is delivered to every subscriber, and each reaction happens independently; the work happens once per subscriber, not once per message.
The consequences reach further than that one change suggests. Ordering: a queue can promise FIFO per lane because it has one delivery target; a pub/sub topic fans out to subscribers moving at independent paces, so ordering is a property of the stream, not of any subscriber’s experience of it. Replay: a queue’s message is transit (once acknowledged, it is gone) while a log-backed topic keeps history, so a subscriber that fell behind, crashed, or was deployed late catches up by replaying it, the durable-log model that kafka architecture makes explicit. Delivery guarantees: at-least-once and exactly-once are anchor terms the message queues article owns, and they apply to every subscriber lane separately; an at-least-once topic with ten subscribers is ten independent at-least-once problems, each solved with the deduplication machinery the idempotency article anchors.
The line between the models is real but deliberately blurred in practice. Consumer groups turn a log-backed topic back into a worklist (a group of subscribers splits the partitions among themselves, each event reaching exactly one member) which is why modern brokers serve both shapes from one substrate. The question to ask is not “which product” but “which shape per flow”: commands that must happen once are worklist problems; facts that many parties should hear are broadcast problems. Most real systems run both, on the same broker, with the shape chosen per topic, and the broker-selection trade-off is kafka vs rabbitmq‘s to referee.
Event sourcing
Event sourcing is a storage pattern in which the sequence of events is the record of truth: state is not updated in place but derived: a projection folded over the events that happened, and rebuildable from them at any time.
The distinction from ordinary pub/sub is sharp and often missed. In plain event driven architecture, the event is a notification: it happens to be useful, and then it is gone, the services’ own databases remain the truth. In event sourcing, the event store is the truth: an aggregate is not a row that gets overwritten but an append-only history that gets extended, and every earlier version of the state remains queryable. The store-versus-broker distinction matters here; a topic is transport, and treating the broker’s retention as your database is how systems discover that retention is a cleanup policy, not a storage contract. When the event store and other state changes must agree atomically, that is the transactional outbox problem; the saga pattern article owns its mechanics, and event sourcing borrows them rather than re-deriving them.
The payoff is real. A full audit trail exists by construction, not bolted on, but inherent in how the truth is written. Temporal questions become answerable: what this account looked like on Tuesday is a replay stopped early. New read models are additive; a projection that joins events into a different shape for a different query is built by replaying history, with no migration and no downtime, and the cache invalidation family of worries shrinks because a projection can always be dropped and rebuilt from the log. Debugging changes character: an incident becomes “replay the events and watch,” which is the same debuggability the durable log provides in kafka architecture.
The costs are just as real. Projections are eventually consistent with the store, by design and by delay; a read model is always some replay-distance behind, and systems that pretend otherwise ship stale data confidently. Events are a schema: once published, they have consumers, and evolution becomes a versioning discipline (additive fields, tolerant readers, upcasting) rather than a table migration. Long-lived aggregates need snapshots, or replay time grows with their history. And subscribers that cannot keep up turn event-sourced systems into flow-control problems; the slow-consumer math is backpressure‘s subject, not this article’s. Event sourcing is a commitment, and the commitment is the point: it pays where the history is the product (ledgers, workflows, audit) and it is decoration where the current state is all anyone ever reads.
Choreography vs orchestration
Choreography vs orchestration is the coordination question at the center of the style: do services react to each other’s events directly (each one a dancer who knows the steps) or does a conductor issue the calls and track the flow? The two names describe where the flow logic lives, and nothing else.
In choreography, there is no coordinator. The order service publishes OrderPlaced; inventory reacts and publishes InventoryReserved; payment reacts to that; email reacts to payment’s event. Each service knows its own triggers and emits its own facts, and the flow is an emergent property: every step a subscription, nobody holding the whole. The strengths are the style’s strengths: no conductor to fail, no central service to scale, new steps added by subscribing without touching the flow’s other participants. The weakness is the mirror image: the flow exists nowhere in particular. “What happens when an order is placed?” has no single answer (it is a grep across the fleet’s subscriptions, and a flow no one can see is a flow no one can debug without the correlation and tracing plumbing that observability tooling) the monitoring and observability article’s subject; exists to provide.
In orchestration, one service owns the flow. The orchestrator subscribes to the triggering event, issues commands to the participants, tracks what completed, and decides what happens next; the steps live in one readable place, the state of the flow is one queryable thing, and compensation logic has an obvious home. The cost is the conductor itself: a service that all flows route through, a scaling and failure concern in its own right, and (if it grows opinions about everything) a monolith quietly reassembling itself at the center of the fleet. The hub article’s trade-off ledger applies here in miniature: microservices architecture decentralized the flow logic in exchange for visibility, and orchestration buys the visibility back by recentralizing it.
The transactional version of this choice is not this article’s to settle. When the steps must either all complete or all undo (money moves, inventory commits) the coordination becomes a saga, and the saga’s own orchestration-versus-choreography comparison, with compensations and the outbox pattern, is the saga pattern article’s subject. This article’s version is the everyday one, and the everyday answer is per-flow, not per-system: short cascades with few steps and no money at stake choreograph beautifully; long flows, flows with compensation, and flows someone will have to explain to an auditor want a conductor. Mixed systems are the norm (choreographed ripples inside an orchestrated transaction, orchestrated seams between choreographed domains) and the mistake to avoid is choosing globally, then discovering that some flows were silently the other kind all along.
Common mistakes
- Commands dressed as events. NotifyUser, SendEmailRequested, UpdateInventory; names like these are instructions wearing fact clothing. If the publisher needs one specific receiver to do one specific thing, the communication is a command and belongs on a worklist, not on a broadcast topic. Faking it inverts the coupling the style exists to provide: every subscriber must now guess which “event” is secretly addressed to it.
- Trusting the dual write. The service commits to its database, then publishes the event: two systems, one truth, two chances to disagree. The commit succeeds and the publish fails, and the rest of the fleet never hears what the database swears happened; the publish succeeds and the commit fails, and the fleet reacts to a fiction. The fix is the transactional outbox (write the event in the same transaction as the state, ship it after) and its mechanics are the saga pattern article’s, borrowed here rather than reinvented.
- Assuming the fan-out preserves order. One stream, ten subscribers, ten paces: each subscriber experiences its own ordering, and retries scramble even that. The queue article’s truth holds here too (ordering holds within a lane, not across lanes) and the honest fix is the same: pin what must be sequenced, or carry sequence numbers and let the consumer reorder. Assuming the broker solved it because a diagram looked sequential is the classic event-stream bug.
- Publishing breaking schemas. An event with subscribers is a contract, and a “small” rename rolls out to every consumer at once. The versioning discipline (additive fields, tolerant readers, new event types instead of mutated old ones) is cheap the day it starts and expensive the day it is discovered. The mistake is treating event evolution like table evolution, which works until the fleet is larger than one team.
- Running choreography without correlation IDs. An event flow without end-to-end correlation is a ghost: the tracing question (which subscriber, reacting to which event, dropped the ball) becomes archaeology across every service’s logs. Correlation belongs in the event envelope from the first publish, and the tracing machinery that consumes it is monitoring and observability territory.
FAQ
What is the difference between an event and a message?
A message is the envelope; events and commands are the letters inside. An event is a fact, past tense, addressed to nobody, publishable to anyone who cares. A command is an instruction: addressed to a specific handler, expecting a specific action. Most “event-driven” bugs start with a command wearing an event’s name, so the vocabulary is worth keeping clean.
Is event driven architecture the same as event sourcing?
No; a communication style versus a storage pattern, and the difference is what the event is for. In plain event driven architecture, events are notifications: useful signals, and the services’ databases remain the truth. In event sourcing, the event store is the truth and current state is a projection of it. You can run one without the other; confusing them is how teams end up adopting a storage model when all they wanted was a broker.
Do you need Kafka for event driven architecture?
No. The style is about who reacts to facts, not about which product carries them; any broker that can fan events out to subscribers will do, and the durable-log model that makes kafka architecture a strong substrate is one implementation, not the definition. When the question becomes which broker fits the workload (replay needs, ordering guarantees, throughput) that selection comparison is what kafka vs rabbitmq exists to referee.
What happens when a subscriber fails in pub/sub?
Its lane stalls; the others do not notice. The broker redelivers (at-least-once by default, which is why subscribers need the deduplication machinery the idempotency article anchors) and when retries run out, the poison event lands in a dead letter queue, the quarantine the message queues article defines. Fan-out’s quiet gift is failure isolation: one broken reaction becomes one stalled lane, not one broken system.
When should you not use event driven architecture?
When the interaction is genuinely request/response (a caller who needs one answer now) or when nobody else needs to react to the fact. Queries, reads, and simple CRUD fit the direct-call shape; forcing them through topics buys latency and operational surface for decoupling nobody uses. And if the system is small enough that one team owns it all, the deeper question is whether the fleet is needed at all, the monolith vs microservices decision upstream of this one.
Related articles
- Next read: service discovery, the reading spine’s next stop: how a fleet that no longer hardcodes addresses finds its own members: the registry, the health checking, and the client-side vs server-side resolution that keep routing honest when instances come and go.
- message queues, the worklist half of the comparison this article completed: delivery guarantees, competing consumers, and the dead letter quarantine, priced before the broadcast model showed up.
- kafka architecture: the durable-log substrate underneath the strongest event streams: how retention, partitions, and consumer groups let one topic serve replay, worklists, and broadcasts at once.
- saga pattern; the transactional version of this article’s coordination question: orchestrated and choreographed sagas, compensations, and the outbox that keeps the event store and the database honest.
- microservices architecture; the cluster hub: the trade-off ledger that explains why a fleet of services needs asynchronous coordination at all, and what it costs.
Last updated on 22 September 2026.