In the previously article of this series I covered about the Observer Pattern. Today, I want to share with you the Publisher and Subscriber pattern, or the PubSub for the closest friends. I highly recommend reading and understanding the Observer Pattern before continuing this quest.
Pub Sub Pattern Design
The PubSub Pattern is a messaging pattern that promotes loose coupling and scalability. This pattern revolves around dispatching messages from publishers to an unspecified number of subscribers or listeners, thereby promoting a many-to-many dependency between objects.
Real case scenario
Let’s consider a chat group application. The Pub/Sub system can handle the sending and receiving of messages efficiently:
Subscribing to a event
When a chat window is opened, it subscribes to an event, such as newMessage
.
This is done using pubsub.subscribe("newMessage", callback)
. The callback function is what will be executed when a new message is published to the newMessage
event. In this case, the callback logs the new message and updates the chat UI.
pubsub.subscribe("newMessage", function(data) {
console.log("New message received:", data);
// Here you would update the chat UI with the new message
});
Publishing to a event
When a user sends a message, it is published to the newMessage event using pubsub.publish("newMessage", messageData). All chat windows subscribed to the newMessage topic will have their callback functions executed with the new message as the argument.
When a user sends a message, it's published to the newMessage
event using pubsub.publish("newMessage", messageData)
. All chat windows subscribed to the newMessage
topic will have their callback functions executed with the new message as the argument.
const messageData = { user: "User A", text: "This is what the user had typed" };
pubsub.publish("newMessage", messageData);
In this way, the Pub/Sub system allows for the decoupling of chat windows (subscribers) and message senders (publishers). The chat windows don't need to know about who sends messages (like we saw on the previously post about the observer pattern), they just need to know what to do when a message is received.
Similarly, message senders don’t need to know who will receive the messages; they just need to send messages to the right topic.
How to build a pub sub
To construct a PubSub system, we need to maintain a record of events or 'topics' and their respective subscribers. This can be done with a simple JavaScript object. When a new message is published, we look up the associated subscribers and execute their callback functions. This allows for a dynamic and flexible system where publishers and subscribers can be added or removed at runtime.
class PubSub {
static events = {}; // It has the an empty list of events
// The subscribe method takes an event name and a callback function
subscribe(eventName, callback) {
if (!this.events[eventName]) {
// If the event doesn't exist yet, initialize it as an empty array
this.events[eventName] = [];
}
// Push the callback function into the array of callbacks for the given event
this.events[eventName].push(callback);
}
// The publish method takes an event name and data
publish(event, data) {
// If the event doesn't exist, or there's no subscribers for this event, return
if (!this.events[event]) {
return;
}
// For each subscriber of this event, call the callback function with the provided data
this.events[event].forEach((callback) => {
callback(data);
});
}
}
Let me break down this code a bit:
The PubSub
class has a static events
property, which is an object that will store all the events (or topics) and their corresponding subscribers (callback functions).
The subscribe
method is used to register a new subscriber for a given event. It takes an event name and a callback function as arguments. If the event does not exist yet, it initializes it as an empty array in the events
object. Then, it adds the callback function into the array of callbacks for the given event.
The publish
method is used to publish new data to an event. It takes an event name and the data to be published as arguments. If the event does not exist or there are no subscribers for this event, it simply returns and does nothing. If there are subscribers, it calls each subscriber's callback function, passing the published data as an argument.
In the context of a chat application, the subscribe
method would be used to register a new chat window that should receive new messages, and the publish
method would be used to send a new message to all chat windows that have subscribed to receive new messages.
You can read more about how to use the publish and subscribe method across devices with the article: Understanding and implementing Event-Driven Communication in Front-End Development.
Real-time Data Engine
This is an important part of the architectural of a software development, and yet, in the majority of cases, not the business core of what you are building. Creating a reliable and scalable PubSub system that syncs data between different instances of our application can be challenging.
We are in 2024, meaning that there is already a solution to it: the Real-time Data Engine tools, specifically the SuperViz SDK. It offers a real-time collaboration and communication SDK and API, designed for developers building real-time web applications.
Using SuperViz you can create a room with several participants that when publishing an event it will be broadcasted to all the participants in the room that are accessing it through different devices and networks. This means that any updates made by one participant will be reflected in real time across all devices, providing a seamless and collaborative experience.
SuperViz provides the infrastructure necessary to build real-time, collaborative applications. This includes the ability to also catch this events on your backend using webhooks, and as well to publish an event with a simple HTTP Request, to name a few features.
Please let me know in the comments what other design patterns you would like to learn about, and don’t forget to share your knowledge!