Module 27 / 46 · Phase F — Event-Driven at Scale · 40 min

Pub/Sub
patterns &
topologies.

A broker is a wire. Now you need to decide who gets which messages — and how. Direct, fanout, topic-pattern, work queue, dead-letter: five patterns that turn "we have Kafka" into "we have a working event-driven architecture."

// What you'll know by the end

  • The four routing strategies brokers offer
  • The five canonical messaging patterns
  • How to choose topic granularity
  • Dead-letter queues and retry strategy design
§ 01 — The wiring problem

A broker is a wire.
Now connect it.

M.26 set up Kafka. Brokers, partitions, consumer groups, ISRs — the infrastructure. The next question is also the harder one: how do you actually shape the flow of events through the system? Which events go to which topics? Which consumers receive which messages? What happens when a message can't be processed? Owning a broker doesn't mean having an architecture. Without explicit decisions about routing, topology, and failure handling, you end up with the worst of both worlds: the operational cost of a broker and the coupling of a monolith.

// THE QUESTIONS A WIRING PLAN HAS TO ANSWER
"Which consumers care about this event?"
The routing question. Some events need to fan out to twelve teams. Others need to go to exactly one worker. Brokers offer four canonical mechanisms — direct, fanout, topic patterns, headers — and the choice determines how loosely or tightly coupled the system stays.
"Do I want one big topic or many small ones?"
The granularity question. One events topic is operationally simple but forces every consumer to filter. Hundreds of topics give surgical subscriptions but explode your topic count and schema-registry burden. Neither extreme is right; the trade-off is the discussion.
"What happens when a consumer can't process a message?"
The failure question. Network glitch? Retry. Bad data forever? Don't loop infinitely. Dead-letter queues are how production-grade systems quarantine broken messages without halting the whole pipeline. They're the difference between "one bad event" and "the whole system jammed."
"How do I know consumers and producers agree on the message shape?"
The contract question. A producer evolves; consumers shouldn't break. Schema registries and event versioning are the answer — and they're as critical as the broker itself once you're past the proof-of-concept stage.

This module is the field guide. §02 covers routing (the four mechanisms brokers use to decide who gets what). §03 covers patterns (the five canonical message-flow shapes — broadcast, work queue, request/reply, pipeline, DLQ). §04 is an interactive routing playground where you click a routing strategy and watch a message flow through the topology. §05 covers topic design — the granularity decision that quietly shapes every team's work for years.

Owning a broker isn't owning an architecture. The wiring is where the architecture lives.
§ 02 — Routing: the four mechanisms

How brokers decide
who gets what.

Every broker has to answer the same question for every message: "of all the queues / topics / subscribers that exist, which ones should this message land in?" Four mechanisms have emerged as the canon — formalized by RabbitMQ's exchange types in the early 2000s, then adopted (under different names) by every broker since. Knowing them is the literacy: every routing decision in every broker is some combination of these four.

// THE FOUR ROUTING MECHANISMS · ARRANGED FROM SPECIFIC TO BROAD

Direct routing
// exact match on routing key
if msg.routing_key == queue.binding_key:
  deliver
Producer attaches a routing key to every message. Each queue binds with one or more exact-match keys. Simplest possible mechanism: if the keys match, deliver; otherwise, skip. Used when you know exactly which downstream consumer each event type belongs to. The default for unicast messaging.
Fanout broadcast
// ignore key, deliver to everyone
for each bound queue:
  deliver
// routing_key ignored
Routing keys are completely ignored. Every queue bound to the exchange gets every message. Maximum broadcast efficiency — one publish, N deliveries. Used for system-wide announcements, cache invalidation, configuration updates. The "tell everyone" pattern.
Topic pattern matching
// wildcard match on key segments
* = one segment
# = zero or more
orders.*.created matches
orders.us.created
Routing key is a dotted hierarchy (e.g., orders.us.created); binding key uses wildcards. The most expressive mechanism: lets consumers subscribe to slices of the event stream without knowing every event type. Used when event taxonomies have natural hierarchies (region, type, severity).
Headers matching
// arbitrary key-value attributes
match on {
  "format": "pdf",
  "priority": "high"
}
Routing happens on structured headers instead of a single string key. Most flexible, rarely used. Most production systems pick one routing primitive (usually topic) and stick with it — the flexibility of headers is usually a sign of an under-designed event taxonomy.

RabbitMQ exposes all four as exchange types you configure explicitly: direct, fanout, topic, headers. Kafka takes a different conceptual path — there are no exchanges, only topics. The same routing intents are expressed via topic design: one topic per direct route, one topic with many consumer groups for fanout, multiple topics consumed by one consumer for topic patterns. The mechanisms are the same; the spelling is different.

// SAME PATTERN, TWO BROKER WORLDS

ROUTING INTENTRABBITMQ-STYLEKAFKA-STYLE
Direct Direct exchange + one queue per routing_key One topic per event type: order-created, payment-completed. Consumers subscribe to specific topics.
Fanout Fanout exchange + many bound queues One topic + multiple consumer groups. Every group reads every message independently — analytics, billing, search, etc.
Topic pattern Topic exchange + queues with wildcard bindings (orders.*.created) Consumer subscribes to multiple topics via regex (subscribe(Pattern.compile("orders\\..*\\.created"))) or filters in application code.
Work queue One queue + multiple competing consumers One topic + one consumer group with multiple consumers. Each partition assigned to exactly one consumer.

The choice between RabbitMQ-style and Kafka-style isn't about which is "better." It's about where the routing logic lives. In RabbitMQ, the broker is opinionated — exchanges and bindings do the routing for you, and consumers just bind queues with the patterns they want. In Kafka, the broker is minimal — it just stores topics; consumer applications express their subscription intent. The first is easier when routing rules change frequently; the second is more flexible when you want consumers to apply complex filtering or stateful logic.

Same routing patterns, different layers. RabbitMQ pushes routing down to the broker; Kafka pushes it up to the consumer.
§ 03 — The five canonical patterns

Five message flows.
That's it.

Routing is the mechanism. Patterns are the shapes you build with that mechanism — the five canonical message flows that virtually every event-driven system uses some combination of. Most distributed-systems books treat these as obvious or scatter them across chapters; we'll line them up here so the catalog is one page in your head.

// THE FIVE CANONICAL MESSAGING PATTERNS

1
Publish / Subscribe (broadcast)
"One event, many independent reactions."

A producer publishes an event; N independent consumers each react to it. The producer doesn't know how many consumers exist or what they do. Each consumer reads the whole stream at its own pace. The bedrock pattern of event-driven architectures. Implemented via fanout exchange or multiple consumer groups in Kafka.

// USE CASES
user.signup → welcome email + create profile + analytics + onboarding workflow
2
Work Queue (competing consumers)
"One job, exactly one worker takes it."

Producer publishes tasks; a pool of workers competes to consume them. Each task is processed by exactly one worker. Load balances naturally — fast workers grab more. The default for parallel processing: image encoding, PDF rendering, ML inference jobs. Implemented via single queue + many consumers (RabbitMQ) or single consumer group with N consumers (Kafka).

// USE CASES
video-upload → 1 of N encoder workers handles it
3
Request / Reply
"Send a message, expect a response."

Producer publishes a request with a correlation ID and a reply-to address; the responder publishes back to the reply queue, including the correlation ID. The original requester matches incoming replies to outgoing requests. Brings async benefits to RPC-style interactions — but adds complexity. Use sparingly; if you find yourself doing this often, your services might want to be more event-shaped.

// USE CASES
price-quote, currency-convert, third-party API proxy
4
Pipeline (chained stages)
"Output of stage N is input of stage N+1."

A message flows through multiple processing stages, each reading from one topic and writing to the next. Each stage scales independently; backpressure naturally builds in topic depth. The workhorse of stream processing — ETL pipelines, ML feature engineering, data enrichment. Kafka Streams and Apache Flink are purpose-built for this.

// USE CASES
raw-events → cleaned → enriched → aggregated → warehouse
5
Dead Letter Queue (DLQ)
"Where messages go to die — on purpose."

Messages that fail processing repeatedly are routed to a separate DLQ instead of being retried forever (or dropped silently). An operator inspects the DLQ, fixes the data or the code, and either replays or discards. The pattern that prevents one bad message from jamming the entire pipeline. Effectively a "broken queue" that's part of every healthy system.

// USE CASES
poison messages, schema mismatches, bugs in 0.01% of inputs

Most production systems use multiple patterns simultaneously. A user-signup event might be pub/sub-broadcast (everyone reacts), then trigger a work-queue task (send the welcome email — pool of workers), with a DLQ catching the inevitable malformed entries. Recognizing which pattern each part of your system is using — and noticing when a pattern is being used badly — is the skill. The next section is the playground where you watch each one play out.

Most production systems run all five patterns at once. The skill is recognizing which is which.
§ 04 — Routing playground · interactive lab

Click. Route.
Watch it land.

Five tabs. Each is a complete topology for one of the canonical patterns. Hit Send with different routing keys; watch the message flow through the exchange, evaluate its rules, and land in the matching queues. The delivery counts on the right tick up — or don't — depending on the routing logic.

PUBSUB.SIM // m.27 lab
// SEND:
// DIRECT ROUTING
PATTERN INFO
Loading...
Loading...
Loading...
// delivery counts
§ 05 — Topic design: coarse vs fine

One topic for everything?
Or one per event?

The most consequential pub/sub decision isn't routing strategy — it's topic granularity. Coarse-grained topics (one giant events stream) and fine-grained topics (one per event type) look almost identical at the start. They diverge wildly at scale. Most teams pick one extreme, then spend a year migrating to the other. Knowing the trade-off up front saves the migration.

// THE TWO ENDS OF THE TOPIC-GRANULARITY DIAL

// COARSE-GRAINED
One topic for everything
"Put all events into events. Consumers filter."

A single topic (or a handful) carries every kind of event. Consumers read everything and discard what they don't care about. Simple to set up, very few topics to manage, easy to add new event types without coordination. Falls apart at volume: every consumer pays the cost of every event, even ones they ignore.

WORKS FOR:
Small systems, MVPs, low event volume, exploratory phases. Internal tools.
// FINE-GRAINED
One topic per event type
"Each event class gets its own topic. Surgical subscriptions."

Every distinct event type (order-created, payment-completed, user-signed-up) gets its own topic. Consumers subscribe only to what they actually need. Efficient at scale — no filtering overhead. Operational tax grows: hundreds of topics, schema registry per topic, partition planning per topic, more knobs to mis-tune.

WORKS FOR:
Mature systems, high volume, well-defined event taxonomies, mid-sized engineering orgs.

The honest compromise most large systems converge on: topic-per-bounded-context, hierarchical names. One topic for orders (orders), one for payments (payments), one for users (users) — not one for every single event subtype. Each topic carries multiple event types within the same domain, distinguished by a type field in the schema. Consumers can subscribe to orders as a stream and pattern-match on event type internally. This gives you most of the efficiency benefits of fine-grained without the operational tax of topic-per-event.

Two related concerns ride on top of this. First, the schema registry: a service that holds every event schema (typically Avro, Protobuf, or JSON Schema) with versioning. Producers register schemas before publishing; consumers fetch them before deserializing. This is what keeps producers and consumers in sync as the system evolves. Confluent Schema Registry is the canonical implementation; AWS Glue Schema Registry and Apicurio are alternatives. Without a schema registry, every breaking change becomes a cross-team coordination event.

Second, event versioning and backward compatibility: when you change an event's shape, old consumers shouldn't crash. The rule of thumb is "add fields, never remove or rename" — additive changes are safe; destructive ones aren't. Mature event-driven orgs treat their event schemas like public APIs — because they are. Other teams build on top of them; breaking them ripples through the system. Every change is a release.

Coarse topics are easy until you need filters. Fine topics are right until you need joins. The middle is usually where you should land.
§ 06 — Eight words for the wiring conversation

Vocabulary,
for the topology.

The terms every event-driven architecture review, every broker doc, and every retro on "why did this break" assumes you know.

Exchange
/ɪksˈtʃeɪndʒ/
In RabbitMQ-style brokers: the named entity producers publish to, which routes messages to bound queues. Kafka has no exchange concept; topics serve a similar role with different mechanics.
Routing Key
/ˈruː.tɪŋ kiː/
A string attached to each message that exchanges use to decide where it goes. In topic exchanges, hierarchical (orders.us.created) and matched against binding-key patterns.
Binding
/ˈbaɪn.dɪŋ/
A rule connecting an exchange to a queue, specifying which routing keys (or patterns) should be delivered. The "wiring" between producers and consumers.
DLQ · Dead Letter Queue
/dɛd ˈlɛt.ər/
A queue for messages that failed processing repeatedly. Prevents one poison message from blocking the pipeline. Operators inspect, fix, and replay or discard.
Correlation ID
/ˌkɒr.əˈleɪ.ʃən/
A unique identifier attached to a request so the response can be matched to it. Required for request/reply over asynchronous brokers, where responses arrive on shared reply queues.
Schema Registry
/ˈskiː.mə/
A central service holding the schemas of every event in the system, with versioning. Keeps producers and consumers in sync as events evolve. Confluent, AWS Glue, Apicurio.
Backpressure
/ˈbæk.prɛʃ.ər/
The signal that consumers can't keep up with producers — visible as growing topic lag. Healthy systems detect it and scale consumers up (or slow producers down) before queues overflow.
Topic Granularity
/ˌɡræn.jʊˈlær.ə.ti/
The design choice of how much each topic carries — one event type, one domain, or everything. The most consequential and hardest-to-reverse topology decision.
§ 07 — Knowledge check

Five questions.
Match the pattern.

Test the wiring intuition. Click an answer; explanation drops in instantly.

QUESTION 1 OF 5
Loading question...
Score: 0 / 5
5 / 5

Wired.

Perfect. Routing, patterns, topic design — all yours. Now you can shape the broker into an actual architecture. Next up: events as the source of truth.

§ 08 — The recap

Three ideas to
carry forward.

The wiring vocabulary you'll bring to every event-driven design review for the rest of your career.

i

Four routing mechanisms

Direct, fanout, topic-pattern, headers. Every broker exposes some combination of these. Picking the right one for each event type is the first design decision.

ii

Five canonical patterns

Pub/sub, work queue, request/reply, pipeline, DLQ. Production systems run several at once. The skill is recognizing each in the wild and knowing when to reach for which.

iii

Topic granularity matters most

Coarse is easy until it isn't. Fine is right until the operational tax bites. Topic-per-bounded-context is usually the right middle ground. Add a schema registry from day one.

↓ UP NEXT

M.28 — Event
sourcing.

You're publishing events. What if the events are the source of truth? Storing the log instead of just sending through it. The architecture pattern that powers banking systems, audit trails, and time-travel debugging — and changes the way you think about state forever.

Continue to Module 28 →