Cloud · Messaging · DWG NO. 031

Publisher-Subscriber

Decouple producers from consumers through a broker and topics, so a publisher never needs to know who's listening.

TypeMessaging
ScopeMany producers, many consumers
ComplexityMedium
Common inEvent-driven microservices, notifications, analytics fan-out

Producers shouldn't need a list of every service that cares about their events

An `OrderPlaced` event might need to reach inventory, billing, analytics, and email — today. If the order service calls each of those directly, adding a fifth interested service next quarter means changing the order service's code again, even though the order service itself hasn't changed.

Publish to a topic, not to a service

Publishers send messages to a named topic on a broker, with no knowledge of who's subscribed. Any number of subscribers can register interest in that topic and each independently receives a copy of every message — subscribers can be added or removed without touching the publisher at all.

OrderServicepublishTopic: OrderPlaced(broker)BillingAnalyticsEmail

A tiny pub/sub broker

`orders.publish(...)` doesn't know or care that three subscribers exist — adding a fourth is one `subscribe` call, with zero changes to the publisher.

pubsub-broker.js
class Broker {
  #topics = new Map();

  subscribe(topic, handler) {
    if (!this.#topics.has(topic)) this.#topics.set(topic, new Set());
    this.#topics.get(topic).add(handler);
  }

  publish(topic, message) {
    for (const handler of this.#topics.get(topic) ?? []) {
      handler(message);
    }
  }
}

const broker = new Broker();

broker.subscribe("OrderPlaced", (order) => console.log(`billing: charge for ${order.id}`));
broker.subscribe("OrderPlaced", (order) => console.log(`analytics: logged ${order.id}`));
broker.subscribe("OrderPlaced", (order) => console.log(`email: confirmation for ${order.id}`));

// OrderService only ever does this — it has no idea who's listening:
broker.publish("OrderPlaced", { id: "order-42" });

Trade-offs