152
behavioral messaging

Publish-Subscribe

Reference Wikipedia ↗
Publish-Subscribe — class diagram
Plate 152 class diagram

The Publish-Subscribe pattern defines one-to-many dependencies between objects. A publisher (or event source) doesn’t know about its subscribers. Instead, it publishes events to a broker (or message queue), and subscribers express interest in specific events by registering with the broker. When an event occurs, the broker efficiently delivers it to all registered subscribers.

Usage

The Publish-Subscribe pattern is frequently used in scenarios requiring loose coupling and event-driven architectures. Common use cases include:

  • Real-time updates: Applications needing to react immediately to changes (e.g., stock tickers, news feeds).
  • Event logging and monitoring: Capturing and distributing system events for analysis and auditing.
  • Decoupled microservices: Allowing services to communicate without direct dependencies.
  • GUI frameworks: Notifying UI elements when underlying data changes.
  • Messaging systems: Implementing asynchronous communication between applications and components.

Examples

  1. Node.js EventEmitter: Node.js’s core EventEmitter class implements the Publish-Subscribe pattern. Modules can emit named events, and other modules can listen for those events using the on() method. The EventEmitter acts as the broker.

    javascript const emitter = new EventEmitter();

    // Subscriber emitter.on(‘data’, (data) => { console.log(“Received data:”, data); });

    // Publisher emitter.emit(‘data’, {message: ‘Hello, world!’});

  2. RxJS (Reactive Extensions for JavaScript): RxJS provides a powerful and flexible way to implement reactive programming, heavily based on the Publish-Subscribe pattern using Observables and Observers. Observables are the publishers, and Observers are the subscribers.

    javascript import { Observable } from ‘rxjs’;

    // Publisher (Observable) const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); });

    // Subscriber (Observer) observable.subscribe( value => console.log(‘value:’, value), error => console.log(’error:’, error), () => console.log(‘completed’) );

Specimens

15 implementations
Specimen 152.01 Dart View specimen ↗

The Publish-Subscribe (Pub/Sub) pattern decouples message senders (publishers) from message receivers (subscribers). Publishers don’t know who their subscribers are, and subscribers only know of the publishers, not how to directly interact with them. A central message broker (often called a topic or event bus) manages message delivery.

This Dart implementation uses a Subject class to act as the message broker. Publishers call notify() on the subject, providing a message. Subscribers register with the subject via a StreamSubscription to receive these messages. Dart’s Streams and StreamController are naturally suited for this pattern, providing a reactive and efficient way to manage asynchronous event handling. The use of StreamController and Stream aligns with Dart’s asynchronous programming model and promotes a clean separation of concerns.

// subject.dart
import 'dart:async';

class Subject {
  final StreamController<String> _controller = StreamController<String>();

  Stream<String> get stream => _controller.stream;

  void notify(String message) {
    _controller.sink.add(message);
  }

  void close() {
    _controller.close();
  }
}

// main.dart
import 'subject.dart';

void main() {
  final subject = Subject();

  // Subscriber 1
  subject.stream.listen((message) {
    print('Subscriber 1 received: $message');
  });

  // Subscriber 2
  subject.stream.listen((message) {
    print('Subscriber 2 received: $message');
  });

  // Publish messages
  subject.notify('Hello, Subscribers!');
  subject.notify('Another message.');

  // Clean up
  subject.close();
}