Applying publisher subscriber design pattern in javascript

Problem

MeowLand is ruled by King Whiskers. Recently, MeowLand has experiencing rapid growth. One night, King Whiskers decide that he needs to upgrade the knowledge and skills of the catizens of MeowLand in order to improve the CDP and sustain the growth of the population. He aims to do that by regularly disseminating information from the palace library.

However, he knows that his catizens are busy cats and he does not want to overwhelm them with irrelavant news.

The catizens are largely distributed across this industry:

  • Mouse hunter
  • Bird hunter
  • Comedian
  • Glam Vloggers
  • Daredevils

Solution

Looking at the problem, we can form a few premises.

  • Information that needs to be pushed
  • Catizens will receive the information
  • Information will be disperse based on the topic of interest relevant to the catizen
  • Catizens can have more than one topic of interest
  • Catizens can change interest and stop update from a particular topic

For this problem, we will look at the publisher/subscriber pattern

Publisher/Subscriber Pattern

The Publisher/Subscriber pattern or the PubSub pattern for short, is a behavioral design pattern. As it's name suggest, it is a design pattern for a subject (Publisher) to notify changes to a list of observers (Subscriber).

Perhaps, you have use the RxJS library, and the term Observer seems familiar. You are right, the PubSub design pattern is a variation of of the Observer pattern and RxJS utilises this pattern. Another common implementation of the observer pattern will be the connect method in Redux.

Observer pattern define by GoF

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
-- Design Patterns: Elements of Reusable Object-Oriented Software

The benefit of implementing an Observer pattern is that two objects are loosely coupled and minimize interdependency between objects. However, because of decoupling, it can sometimes become difficult to ensure that the various parts of the application is working as part of the application can crash without affecting other part of the system.

There is a small difference between the two patterns. For the PubSub pattern, there is a topic/event channel that sits between the publisher and the subscriber. This event system allows the subscriber to be notify of specific events.

Code

Now that we have the overview of this design pattern, we will create the code for King Whisksers.

Firstly, we will create the Publisher class, which will register the subscribers and publish events to the subscribers. For the method subscribe, the argument event will be the key that the subscribers is listening to. In our case, a bird hunter will need to listen to climb trees events.

When King Whiskers will like to publish a climb trees news, we will find the climb trees key in subscribers and invoke the callback register during subscription.

To simplify unsubscription, we will pass the unsubscribe method to be register with the subscribers. In a perfect world, we will create an advanced PubSub which will handle the unsubscripton through the event and the topic but that will introduce a more abstracted code.

To understand how to implement a PubSub with nested arguments, check out Jack Lawson's Mediator.js.
Read: Mediator.js
class PubSub {
  constructor() {
    this.subscribers = {};
  }

  subscribe(event, callback) {
    if (!this.subscribers[event]) {
      this.subscribers[event] = [];
    }
    const index = this.subscribers[event].push(callback) - 1;
    const { subscribers } = this;

    return {
      unsubscribe: function() {
        subscribers[event].splice(index, 1);
      },
    };
  }

  publish(event, data) {
    if (!this.subscribers[event]) {
      return;
    }
    this.subscribers[event].forEach(subscriberCallback =>
      subscriberCallback(data)
    );
  }
}

Next, we will create our Cat class. Do note that, as mention above, in the perfect world, our Cat class will not need to handle subscription.

class Cat {
  constructor(name, interests) {
    this.name = name;
    this.interests = interests;
    this.unsubscribe = {};
  }

  addUnsubscription(keyName, method) {
    this.unsubscribe[keyName] = method;
  }
}

Then, we will set up the PubSub and test if everything is working based on the 5 premises mention above.

const catDomPubSub = new PubSub();

const cat1 = new Cat('Midnight', ['climb trees', 'hunt', 'weather']);
const cat2 = new Cat('Bear', ['humour', 'weather', 'camera skills']);
const cat3 = new Cat('Smokey', ['hunt', 'camera skills']);
const allCat = [cat1, cat2, cat3];

allCat.forEach((singleCat, idx) => {
  const { name, interests } = singleCat;
  interests.forEach(interest => {
    const { unsubscribe } = catDomPubSub.subscribe(interest, data =>
      printInterestReceived(name, interest, data)
    );
    allCat[idx].addUnsubscription(interest, unsubscribe);
  });
});

function printInterestReceived(name, interest, data) {
  console.log(`${name} has received information for ${interest}: ${data}`);
}

catDomPubSub.publish('climb trees', 'Learn coordination');
catDomPubSub.publish('weather', 'Might rain tomorrow, stay indoors!');
catDomPubSub.publish(
  'hunt',
  'Predicted migration of house rats tomorrow, stay alert'
);

cat1.unsubscribe.hunt();

catDomPubSub.publish('hunt', 'Sharpen your claws');

If we run this code, we will see the following.

Midnight has received information for climb trees: Learn coordination

Midnight has received information for weather: Might rain tomorrow, stay indoors!
Bear has received information for weather: Might rain tomorrow, stay indoors!

Midnight has received information for hunt: Predicted migration of house rats tomorrow, stay alert
Smokey has received information for hunt: Predicted migration of house rats tomorrow, stay alert

Smokey has received information for hunt: Predicted migration of house rats tomorrow, stay alert

Because Midnight unsubscribes from hunt, the last publish of hunt will not show Midnight.

Finally, we can show our product to King Whiskers.

This is the gist of a simple example of the PubSub model.

Is this consider completed? No, because we did not store the notifications in each individual Cat. For example, the cat could be a level of each skillsets that you are keeping track of based on the publications they receive. Upon each update, they will improve their experience require to level up. Till next time, we will explore more alternatives with the Observer and PubSub design pattern.

Join the geek movement

Get updated with our latest posts