Asynchronous Communication

System Design · Asynchronous Communication

Pub/Sub Patterns & Event-Driven Architecture

Why decoupling producers from consumers changes how systems scale and fail — and what it costs you in traceability.

8 min read
Pub/SubEvent-Driven Architecture

Most request/response thinking carries over cleanly to a single message queue — a producer puts work on a queue, one consumer picks it up, done. Pub/sub breaks that mental model on purpose. A publisher emits an event onto a topic without knowing, or caring, whether anyone is listening. Zero subscribers, one subscriber, or a dozen — the publisher's code doesn't change. That single property is the seed from which most event-driven architecture grows, for better and for worse.

Point-to-point vs. publish/subscribe

In a point-to-point queue, a message has exactly one logical consumer — even with multiple workers behind it, they're competing for the same message, and only one of them gets it. It models a work queue: "someone do this job." Publish/subscribe models a different relationship: "this happened." Every subscriber that cares gets its own copy of the event, independently of every other subscriber. Adding a fifth subscriber to a topic doesn't take work away from the other four — it just means a fifth thing now reacts to the same fact.

That distinction is why pub/sub fits domain events so well. When an OrderPlaced event fires, billing needs to charge a card, inventory needs to reserve stock, and analytics needs to log a conversion. None of those consumers should have to coordinate with each other, and none of them should block the others from existing.

Decoupling in time and topology

The obvious benefit of pub/sub is that the publisher doesn't need a compiled-in list of consumers — no import of a billing client, no direct network call to inventory. That's decoupling in topology: services can be added, removed, or replaced behind a topic without touching the publisher at all. Less obvious, but just as important, is decoupling in time. A message broker with durable storage lets a subscriber be offline, slow, or mid-deploy when an event is published and still process it later once it catches up. The publisher never blocks waiting for a consumer to be ready.

This is what makes independent deployment and independent scaling possible. The order service can ship ten times a day without coordinating a release with billing. Inventory can scale its consumer fleet up during a flash sale without inventory's existence even being visible to the order service's deployment pipeline. The cost, as we'll get to, is that the overall system becomes harder to reason about as a whole precisely because no single component holds the full picture.

Event notification vs. event-carried state transfer

Not all events carry the same amount of information, and the choice has real consequences.

An event notification is deliberately thin — often just an ID and a type, like { orderId: "o_123", type: "OrderPlaced" }. A subscriber that wants details calls back into the order service to fetch them. This keeps events small and guarantees the subscriber always sees current data, but it re-introduces a synchronous dependency on the publisher's API at the exact moment you were trying to avoid one — and if the order service is down, so is every subscriber's ability to react.

Event-carried state transfer goes the other way: the event itself carries the full state a subscriber needs — the order, its line items, the customer's shipping address — so no callback is required. Subscribers become fully autonomous and can keep working even if the publisher is unreachable. The trade-off is payload size, staleness (the subscriber has a snapshot from the moment of publication, not a live view), and a wider public contract, since more fields are now part of what other services depend on.

Choreography vs. orchestration

Once several services react to each other's events, you've built an event-driven business process, and there are two ways to run one. In choreography, there's no central brain: the order service publishes OrderPlaced, billing reacts and publishes PaymentCaptured, inventory reacts to that and publishes StockReserved, and shipping reacts to that. Each service only knows about the events immediately before and after its own step.

In orchestration, a coordinator (often a saga or workflow engine) explicitly calls each step, tracks where the process currently stands, and decides what happens next, including compensating actions if something fails partway through.

Visibility vs. coupling

Choreography keeps services maximally decoupled but makes the overall process implicit — it exists only as the sum of everyone's event handlers, and no one file describes it. Orchestration makes the process explicit and easy to inspect at the cost of a central component every participant now indirectly depends on. Many teams choreograph the easy, high-volume paths and orchestrate the few processes complex enough to need visible state, like multi-step refunds.

Where this goes wrong

The most common failure is implicit coupling through the event schema itself. Nobody calls it an API, so nobody versions it like one — until three downstream services depend on a field named status, and renaming it breaks all of them simultaneously, with no compiler and no test suite to catch it ahead of time. Treat published event schemas as seriously as a public REST contract: version them, deprecate fields with notice, and keep a registry of who consumes what.

The second is debuggability. When a business process is spread across a dozen services that each independently react to events, there is no stack trace for "why didn't this order ship." The practical mitigation is distributed tracing: stamp a correlation ID on the originating event and propagate it through every downstream event and log line, so a single trace ID can reconstruct the entire choreography after the fact, even though no single service ever saw the whole thing at once.

Key takeaways

  • Pub/sub decouples publishers from consumers: a publisher doesn't know or need to know who, if anyone, is subscribed.
  • That decoupling spans both topology (no direct dependency on consumers) and time (durable brokers let slow or offline consumers catch up later).
  • Event notifications keep events small but reintroduce a synchronous callback; event-carried state transfer trades payload size and staleness for consumer autonomy.
  • Choreography keeps services independent but makes the overall process implicit; orchestration makes the process visible at the cost of a central dependency.
  • Event schemas are contracts even though they don't look like APIs — version them deliberately.
  • Distributed tracing with a propagated correlation ID is the practical way to debug a business process scattered across independently-reacting services.
2026 — Built by Arpit Mishra