A practical lesson on publish-subscribe as the core one-to-many eventing pattern, including where it works well and where governance becomes difficult.
Publish-subscribe is the classic event-driven communication pattern. A producer publishes one event, and multiple independent consumers can react to it without the producer directly coordinating each one. This is one of the clearest strengths of event-driven architecture because it lets one business fact produce several downstream outcomes without building a hard-coded chain of synchronous calls.
The pattern is strongest when the producer is truly publishing a shared fact rather than signaling one intended task. order.placed, payment.failed, and account.suspended are good examples because several domains may reasonably care. One consumer may build a projection, another may trigger workflow, and another may update analytics. The producer should not need to know all of them in advance.
flowchart LR
A["Producer"] --> B["Topic or pub/sub channel"]
B --> C["Consumer A"]
B --> D["Consumer B"]
B --> E["Consumer C"]
What to notice:
Pub/sub is powerful because it reduces point-to-point coupling. Without it, the producer often becomes a coordinator. It has to know which service should be called for projections, which should be called for notification, and which should be called for analytics. With publish-subscribe, the producer can record the fact and allow consumers to own their own reactions.
This also helps extensibility. A team can add a new consumer later without rewriting the producer, as long as the event contract remains suitable for that new use.
The most important test for pub/sub is whether downstream reactions are genuinely independent. If one consumer exists only to forward the event to the one service that the producer really depends on, the architecture may not be truly benefiting from pub/sub. The stronger fit is when several downstream consumers can react at their own pace and with their own local logic.
Pub/sub gets harder as the number of consumers grows. The producer may accumulate unknown downstream dependencies over time. That can make schema evolution, event retirement, and naming changes much riskier. A producer that once had two consumers may later have fifteen, including one analytics job nobody told the original team about.
This is why event catalogs, ownership, and compatibility discipline matter more in pub/sub-heavy environments. Loose coupling at runtime can still create hidden coupling at contract level if teams stop tracking who depends on what.
1pubsubReview:
2 event: order.placed
3 likelyConsumers:
4 - fulfillment
5 - fraud
6 - notifications
7 - analytics
8 governanceConcerns:
9 - schema evolution
10 - consumer discovery
11 - retirement planning
Pub/sub is a weaker fit when:
In those cases a queue, command message, or direct API may be clearer.
A producer publishes invoice.generated, but one subscriber then republishes it in several slightly transformed forms because other teams wanted different field names. What should you challenge first?
Challenge whether the original event contract is too weak or whether taxonomy and ownership are poorly managed. Pub/sub works best when several consumers can interpret one stable fact, not when each consumer must reshape the meaning for the next consumer.