The publisher that knows no subscribers
A Socratic walk-through of the publisher that knows no subscribers — reasoned out one step at a time, not lectured.
The question we started with
THE QUESTION #How does a system stay extendable by making the sender ignorant of who is listening?
An order is placed. Someone must email a receipt, decrement inventory, credit a loyalty balance, and refresh a fraud model. The obvious code says so directly: the order service calls the mail service, then the inventory service, then loyalty, then fraud. Every effect is visible in one place, which is precisely the property we usually call good design.
Now the marketing team wants a webhook fired for new customers. The change lands in the order service — a service that has nothing to do with marketing. And here is the part worth sitting with: the order service's behaviour did not change at all. It still places orders. Only its list of people to notify grew. Why should a module have to be edited for a change that does not touch what it does?
Reasoning it through
REASONING #Look at what that call list actually encodes. Not "how to place an order" but "who currently cares that an order was placed" — a fact about the rest of the organisation, stored in a module that has no business knowing it. The order service has become the registry of its own consumers, so every consumer's arrival, departure, or renaming is a change to it.
With one producer and n consumers, the direct arrangement means the producer names all n. Add a consumer and you edit the producer, redeploy it, and re-test it — risking the thing that takes the money, for a change to a mailing list.
So ask what could be inverted. The producer holds a fact: this happened. Each consumer holds a reaction: when that happens, do this. In the direct arrangement, the producer owns both halves. Split them and give the pairing to something in the middle — a topic, a broker, an event bus. The producer publishes to the topic, naming no recipient. Each consumer subscribes, naming the topic. Adding the marketing webhook is now a new subscriber and no change at all to the order service.
Notice what has actually moved. The dependency arrow between producer and consumer has not been weakened; it has been reversed. Before, the producer depended on consumers. Now consumers depend on the event. That is the whole mechanism, and it explains the property we wanted: extension without modification, because the extension point is the topic rather than a line of code.
But be precise about what "knows nothing" means. The producer is ignorant of consumer identity, count, and behaviour. It is not ignorant of the event schema — that is a contract, and the moment two subscribers depend on a field, changing it breaks them. The coupling did not vanish; it was concentrated into one explicit, versionable artifact instead of spread across a call list. A genuine improvement, and a smaller claim than "decoupled".
Now the costs, which are considerable and specific.
Control flow becomes invisible. In the direct version you read the order service and see everything that happens; in the event version you see nothing beyond a publish. Answering "what happens when an order is placed?" now needs a registry, a search across repositories, or distributed tracing — and if nobody built that, the answer is genuinely unknown. This is the most common regret about event-driven systems.
Errors have nowhere to go. If the mail service fails during a direct call, the caller learns immediately and can retry or refuse the order. A publisher that has already returned cannot be told. So each subscriber must handle its own failure — retries, a dead-letter queue, alerting — and the system must tolerate an order that succeeded with no receipt sent. Sometimes that is exactly right; the order should not fail because a mail server is down. Sometimes it is disastrous, which is why an inventory decrement is a poor candidate for fire-and-forget.
Delivery guarantees stop being free. Most brokers offer at-least-once delivery, so a consumer will occasionally see the same event twice — after a crash between processing and acknowledgement, say. Handlers must therefore be idempotent, and "exactly once" as usually advertised means effectively-once processing within one system's boundary, not genuine one-shot delivery over a network.
One more distinction decides whether this ages well. Publish a command in disguise — an event called SendReceiptEmail — and you have kept every coupling while adding a broker. Publish a fact — OrderPlaced, past tense, something now true — and the producer genuinely does not know what follows. Past-tense naming is not stylistic; it is the test of whether the inversion happened.
The analogy
THE ANALOGY #Think of a newspaper. The press prints an edition and has no idea who reads it — a subscriber can start or stop without the presses being re-tooled, and a new reader is a new reader, not a new print run. But the press is not free of obligation: it must keep publishing on the same paper size, in the same language, with the front page where readers expect it. Change the format and every reader is affected, even though the press still does not know a single one of them by name.
a newspaper reader who misses an edition simply misses it, whereas a subscriber that fails to process an event usually must not lose it — so the broker has to retain, retry, and eventually quarantine messages, machinery a printing press has no equivalent of and which is where most of the real operational work lives.
Clarifying the model
THE MODEL #The misconception worth correcting is that this pattern removes coupling. It relocates and reshapes it. Behavioural coupling goes down — the producer no longer encodes anyone's reaction — while schema coupling stays and becomes more dangerous in one specific way: since the producer does not know its consumers, it cannot know who a field change breaks. Direct calls at least fail at compile time. This is why mature event systems invest in a schema registry with compatibility rules, and why additive-only evolution becomes a discipline rather than a preference.
Two refinements. First, the choice is per-interaction, not per-system. Effects that must succeed for the operation to be meaningful, or whose failure the caller must act on, generally want a direct call with a real response. Effects that are genuinely other people's business — notifications, analytics, downstream projections — want events. Most healthy systems have both, and the tell for a bad boundary is a publisher that then polls for the result.
Second, ordering is weaker than intuition assumes. Brokers in the Kafka lineage guarantee order only within a partition, so a subscriber that assumes OrderPlaced precedes OrderShipped may be wrong — which makes the partition key, usually the entity's identifier, a modelling decision rather than a configuration detail.
A picture of it
THE PICTURE #How to readStart at the rounded node and follow the single arrow into the topic — that is everything the publisher does, and no consumer name appears anywhere on its side. The three solid arrows out of the store are subscribers reading the same fact independently; the dashed one is the later addition that required no edit upstream. Then follow the receipts branch to the diamond: the "no" edge leads to the hazard this design accepts, an order that stands while its receipt never arrives, which a direct call would have surfaced to the caller instead.
What became clearer
WHAT CLEARED #Ignorance of the audience is what makes the producer stable: because it announces a fact rather than issuing instructions, new reactions attach at the topic instead of inside its code. The coupling is not removed but concentrated into the event's schema — traded for the loss of visible control flow and of any way to tell the publisher that a reaction failed.
Where to go next
ONWARD #- How event-carried state transfer differs from a thin notification, and why the fat version reduces chatter but multiplies stale copies.
Key terms
TERMS #| Term | What it means |
|---|---|
| Publish-subscribe | a messaging arrangement in which producers send to a topic and consumers subscribe to it, with neither naming the other. |
| Event as fact | a past-tense record that something occurred, as opposed to a command instructing a specific recipient to act. |
Every term the collection defines is gathered in the glossary.