Publish-Subscribe
Intent
Enable broker-mediated message broadcasting where publishers send messages to topics without knowing subscribers, and subscribers receive messages from topics they’re interested in without knowing publishers.
Problem
Direct point-to-point communication creates tight coupling between message senders and receivers, making it difficult to add new consumers or scale the system. When multiple components need to react to the same events, duplicating messages or maintaining lists of recipients becomes unwieldy and fragile as the system evolves.
Real-World Analogy
A newspaper publisher doesn’t know who reads their paper, and readers don’t interact directly with journalists. The newspaper (broker) organizes content into sections like Sports, Business, and Entertainment (topics). Readers subscribe to sections they care about, and when journalists publish articles, they go to the appropriate section. New readers can subscribe or unsubscribe without affecting the publisher, and the newspaper handles distribution.
When You Need It
- Multiple consumers need to react independently to the same events
- You want to add or remove event consumers without modifying publishers
- Different subsystems need to process events at their own pace
UML Class Diagram
classDiagram
class Publisher {
+publish(topic, message)
}
class MessageBroker {
+subscribe(topic, subscriber)
+unsubscribe(topic, subscriber)
+publish(topic, message)
-topics: Map~String,Topic~
}
class Topic {
+name: String
-subscribers: List~Subscriber~
+addSubscriber(subscriber)
+removeSubscriber(subscriber)
+broadcast(message)
}
class Subscriber {
<<interface>>
+onMessage(message)
}
class ConcreteSubscriberA {
+onMessage(message)
}
class ConcreteSubscriberB {
+onMessage(message)
}
class Message {
+topic: String
+payload: Data
+timestamp: DateTime
}
Publisher --> MessageBroker : publishes to
MessageBroker --> Topic : manages
Topic --> Subscriber : notifies
Subscriber <|.. ConcreteSubscriberA
Subscriber <|.. ConcreteSubscriberB
Publisher --> Message : creates
Topic --> Message : broadcasts
Sequence Diagram
sequenceDiagram
participant Publisher1
participant Broker
participant SubscriberA
participant SubscriberB
Publisher1->>Broker: Send Message to Topic
Broker->>SubscriberA: Deliver Message
Broker->>SubscriberB: Deliver Message
SubscriberA->>SubscriberA: Process independently
SubscriberB->>SubscriberB: Process independently
Publisher1->>Broker: Send another Message
Broker->>SubscriberA: Deliver Message
Broker->>SubscriberB: Deliver Message
Participants
- Publisher — creates and sends messages to topics without knowing subscribers
- MessageBroker — central hub that manages topics and routes messages
- Topic — named channel that organizes messages by category or type
- Subscriber — interface for components that receive messages from topics
- Message — data package with topic, payload, and metadata
How It Works
- Subscribers register interest in specific topics with the message broker
- Publisher creates a message and sends it to a topic via the broker
- Broker identifies all subscribers registered for that topic
- Message is broadcast to each subscriber independently
- Subscribers process messages asynchronously without blocking the publisher
Applicability
Use when:
- You need to decouple producers and consumers of events
- Multiple independent systems need to react to the same events
- You want dynamic subscription management without changing publishers
Don’t use when:
- You have simple point-to-point communication between two components
- Message ordering and delivery guarantees are critical (requires additional mechanisms)
- The overhead of a message broker is unjustified for your scale
Trade-offs
Pros:
- Complete decoupling between publishers and subscribers
- Easy to add new subscribers without modifying existing code
- Supports dynamic, scalable event-driven architectures
Cons:
- Message broker becomes a potential single point of failure
- Harder to reason about system behavior due to indirect communication
- Delivery guarantees and message ordering require additional complexity
Example Code
C#
using System;
using System.Collections.Generic;
// Message broker for pub-sub
public class MessageBroker
{
private readonly Dictionary<string, List<Action<string>>> _subscriptions = new();
public void Subscribe(string topic, Action<string> handler)
{
if (!_subscriptions.ContainsKey(topic))
_subscriptions[topic] = new List<Action<string>>();
_subscriptions[topic].Add(handler);
Console.WriteLine($"[Broker] Subscribed to topic '{topic}'");
}
public void Unsubscribe(string topic, Action<string> handler)
{
if (_subscriptions.ContainsKey(topic))
_subscriptions[topic].Remove(handler);
}
public void Publish(string topic, string message)
{
Console.WriteLine($"\n[Broker] Publishing to '{topic}': {message}");
if (!_subscriptions.ContainsKey(topic))
{
Console.WriteLine($"[Broker] No subscribers for topic '{topic}'");
return;
}
foreach (var handler in _subscriptions[topic])
{
handler(message);
}
}
}
// Subscriber implementations
class EmailService
{
public void OnOrderPlaced(string message)
{
Console.WriteLine($" [EmailService] Sending confirmation email for: {message}");
}
}
class InventoryService
{
public void OnOrderPlaced(string message)
{
Console.WriteLine($" [InventoryService] Updating stock for: {message}");
}
}
class AnalyticsService
{
public void OnOrderPlaced(string message)
{
Console.WriteLine($" [AnalyticsService] Recording metrics for: {message}");
}
public void OnUserRegistered(string message)
{
Console.WriteLine($" [AnalyticsService] Tracking new user: {message}");
}
}
class Program
{
static void Main()
{
var broker = new MessageBroker();
// Create subscribers
var emailService = new EmailService();
var inventoryService = new InventoryService();
var analyticsService = new AnalyticsService();
// Subscribe to topics
broker.Subscribe("order.placed", emailService.OnOrderPlaced);
broker.Subscribe("order.placed", inventoryService.OnOrderPlaced);
broker.Subscribe("order.placed", analyticsService.OnOrderPlaced);
broker.Subscribe("user.registered", analyticsService.OnUserRegistered);
// Publishers send messages without knowing subscribers
Console.WriteLine("\n--- Publishing Events ---");
broker.Publish("order.placed", "Order #1234 - Widget x5");
broker.Publish("order.placed", "Order #1235 - Gadget x2");
broker.Publish("user.registered", "User: alice@example.com");
broker.Publish("product.viewed", "Product: Widget Pro");
}
}
Runnable Examples
| Language | File |
|---|---|
| C# | pub-sub.cs |
Related Patterns
- Observer — similar intent but pub-sub uses a broker instead of direct subject-observer links
- Mediator — message broker acts as mediator between publishers and subscribers
- Event Sourcing — events can be published to topics for subscribers to process