Requirements for the Pub-Sub System
Functional Requirements
Let's define exactly what our pub-sub messaging system needs to do. Each requirement below is specific enough to drive at least one class, method, or relationship in our final design.
-
Topic Management: The system shall allow creating named topics. Each topic is a unique channel that publishers can send messages to and subscribers can listen on. Topic names are unique -- creating a topic with an existing name returns the existing topic.
-
Message Publishing: A publisher shall be able to publish a message to a specific topic through the broker. The message contains the content payload, the topic name, the publisher's identifier, and a timestamp generated at creation time.
-
Subscribing to Topics: A subscriber shall be able to subscribe to one or more topics through the broker. Once subscribed, the subscriber receives all future messages published to that topic.
-
Unsubscribing from Topics: A subscriber shall be able to unsubscribe from a topic at any time. After unsubscribing, the subscriber stops receiving new messages from that topic. Unsubscribing from a topic the subscriber is not subscribed to is a no-op (no error).
-
Message Fan-Out: When a message is published to a topic, it shall be delivered to all subscribers currently registered to that topic. If a topic has zero subscribers, the message is effectively dropped (no queuing for future subscribers).
-
Subscriber Interface: Subscribers shall implement a common interface with an
on_messagecallback method. This allows any class to become a subscriber by implementing the interface, following the Open/Closed Principle (Fundamentals S6). -
Multiple Publishers per Topic: Multiple publishers shall be able to publish to the same topic independently. The broker does not restrict which publishers can write to which topics.
-
Broker as Central Coordinator: All publishing and subscribing operations shall go through a central broker (the
PubSubBroker). Publishers and subscribers never interact directly -- the broker routes messages from publishers to the appropriate topic's subscribers. -
Topic Listing: The system shall support querying the list of active topics and the number of subscribers on each topic, enabling basic introspection of the system state.
Non-Functional Requirements
Beyond functionality, our system should meet these quality attributes:
-
Extensibility (Open/Closed Principle): Adding a new type of subscriber (e.g., a logging subscriber, an alerting subscriber, a database-writing subscriber) must not require modifying existing classes. The
Subscriberinterface makes this possible -- any new class just implementson_message. -
Loose Coupling: Publishers must have zero knowledge of subscribers, and subscribers must have zero knowledge of publishers. All communication goes through the broker and topics. This decoupling means we can add, remove, or replace any publisher or subscriber without affecting others.
-
Thread Safety (Extension): The system shall support concurrent access -- multiple publishers sending to the same topic simultaneously, subscribers being added or removed while messages are in transit. This is covered in our Thread-Safe Message Delivery extension tutorial.
-
Testability: Each component (Message, Topic, Subscriber, Publisher, Broker) shall be testable in isolation. The
Subscriberinterface enables mock subscribers for unit testing. -
Single Responsibility: Each class has one reason to change --
Messageholds data,Topicmanages its subscriber list,Publishercreates messages,PubSubBrokerroutes and coordinates.
Scope Clarification
In Scope:
- Topic creation and management through the broker
- Message creation, publishing, and fan-out delivery
- Subscribe and unsubscribe lifecycle
- A clean
Subscriberinterface for polymorphic message handling - Thread-safe message delivery as an extension
- Observer and Mediator pattern application
Out of Scope (and why):
- Message persistence / durability -- Systems like Apache Kafka persist messages to disk for replay. This is an infrastructure concern, not object-level design. Our system delivers messages in-memory only.
- Message ordering guarantees -- Real messaging systems guarantee ordering within partitions. Our in-memory system delivers synchronously, so ordering is naturally preserved within a single publisher.
- Consumer groups / load balancing -- Kafka-style consumer groups distribute messages among group members. This is a distributed systems pattern beyond our OOP scope.
- Wildcard / pattern-based subscriptions -- Some systems allow subscribing to
orders.*to matchorders.created,orders.cancelled. We use exact topic name matching. - Message filtering -- Subscribers receive all messages on their subscribed topics; content-based filtering is not included.
- Network transport / serialization -- No TCP, HTTP, or Protocol Buffers. All communication is in-process method calls.
- Dead letter queues / retry logic -- If a subscriber's
on_messagethrows an exception, we don't retry or redirect to a dead letter queue.
Interview Tip
In a real LLD interview, spend 3-5 minutes clarifying the scope of "pub-sub" before designing. The term covers everything from a simple in-memory event bus to Apache Kafka's distributed commit log. Ask your interviewer:
- "Should messages be persisted, or is in-memory delivery sufficient?" -- This determines whether you need message storage classes.
- "Do we need consumer groups where only one subscriber in a group gets each message, or full fan-out to all subscribers?" -- This changes the delivery model fundamentally.
- "Is thread safety required? Can multiple publishers send concurrently?" -- This decides whether you need synchronization.
- "Should subscribers receive messages they missed while offline?" -- This determines whether topics need message history.
A strong candidate scopes the problem before drawing a single class. The requirements above represent a clean, well-scoped in-memory pub-sub system that exercises the core OOP concepts without drowning in infrastructure complexity.