Tyson Cadenhead delivers the first installment on a new ongoing series that dives into Modern Web Best Practices that are followed by appendTo. In this installment, he talks about the Publish/Subscribe Pattern and two open source libraries appendTo uses to implement this pattern
By Tyson Cadenhead
Introduction
At appendTo, we are in the business of creating large scalable JavaScript applications. To keep our applications loosely coupled and modular, we take advantage of Pub/Sub libraries to communicate between modules.
What is Pub/Sub?
Pub/Sub refers to a software design pattern called the “Publisher/Subscriber” pattern. Instead of directly calling a method inside a separate module, we can use the Pub/Sub pattern to broadcast the event. The event can be intercepted by any module that is listening for it. This approach decouples your code and makes it substantially less fragile. By using the Pub/Sub pattern, modules are no longer aware of each other. They are only aware of the events that they are broadcasting or listening for.
Amplify Pub/Sub and Postal
While there are many capable Pub/Sub libraries in the world, our best practices at appendTo dictate that we use either Amplify or Postal.
Amplify
Amplify was created and maintained by appendTo. That becomes pretty obvious when you look at the Amplify site, but I just wanted to get that out in the open so nobody accuses me of touting our own products. Amplify is free and open-source. I promise I’m not trying to sell anything here.
More Than Pub/Sub
Amplify is a JavaScript library that offers a local data storage API and a request abstraction layer in addition to Pub/Sub features. This differs from Postal, which is exclusively intended for Pub/Sub.
Simple API
Subscribing to and publishing is really easy. The following illustrates a basic example where we subscribe to an event. In the world of Pub/Sub, the event name is referred to as a “topic”. By calling amplify.publish
we can activate the event that is listening for the topic that is called.
amplify.subscribe("my.topic", function (data) {
console.log(data); // { foo: "bar" }
});
amplify.publish("my.topic", { foo: "bar" });
Priority
One really useful feature of Amplify is the ability to set the “priority” of each subscription. By adding a number as a third argument to the amplify.subscribe()
method, you can determine which order the subscriptions will be called in if there are multiple subscriptions on the same topic. The priority feature can be really useful for debugging purposes. It can also be usefully for ensuring that subscriptions are called in the correct order in situations where the execution order matters.
Postal
Postal is the creation of Jim Cowart. It is an in-memory message bus that is loosely inspired by AMQP. It can be used in the browser or with Node.
Channels
Postal uses “channels” to separate groups of topics together. You can use any number of channels in your application. Channels are a good way to separate out types of events. Here is an example of Postal using a channel:
var myChannel = postal.channel("channel-name");
myChannel.subscribe("my.topic", function (data, envelope) {
console.log(data); // { foo: "bar" }
});
myChannel.publish("my.topic", { foo: "bar" });
Envelopes
Every subscription that is triggered gets an “envelope” object as its second argument. The envelope is a wrapper around the data that is being published. It contains a slew of metadata such as the channel name, the topic and a timestamp when the event was published.
Wildcard Subscriptions
Probably one of the most useful things about Postal is the ability to create wildcard subscriptions. A wildcard subscription uses the *
symbol to listen for any single word in the topic as separated by a period. For example:
myChannel.subscribe("my.*", function (data, envelope) {});
myChannel.publish("my.topic"); // Triggers the subscription
myChannel.publish("my.other"); // Triggers the subscription
myChannel.publish("your.topic"); // Does not trigger the subscription
The wildcard topic subscriptions are awesome for listening for any changes on a certain root word indiscriminately.
Amplify or Postal?
Both Amplify and Postal are really great and useful libraries. Each is very mature, well tested and well maintained. In the end, the choice comes down to the needs of your specific project.
If you need to use the other non-Pub/Sub features that come packed with Amplify, it is a really solid choice. Amplify is probably the easiest to get your head around because there are fewer options. That makes it quick and easy to get started with.
On the other hand, Postal offers a plethora of features that Amplify doesn’t support. For larger and more complex projects, especially if you aren’t using the Amplify request or store components, Postal is a really great option.
You really can’t go wrong with either library. If you have any doubt about which one to use, I would suggest creating an abstraction layer around your Pub/Sub library of choice. That way, you can easily switch to a different library if needed. Whichever you chose to go with, Pub/Sub will dramatically improve the organization and stability of your app.
Nice article, I agree that Pub/Sub is great for decoupling the components of a large application, I use JS-Signals for this which works great when you have dependency injection in your project https://millermedeiros.github.io/js-signals/
Nice article. I have been using this pattern for a while now but by no mean am I an expert. So I need a clarification.
You wrote “In the world of Pub/Sub, the event name is referred to as a “topic”. By calling amplify.publish we can activate the event that is listening for the topic that is called.”
I thought subscribers were listening for events. Don’t you mean “…we can activate the subscriber(s) that is (are) listening for the topic that is called.”
amplify.publish publishes (or “activates” in Tyson’s words) the event that is being listened to by a subscriber for the given topic. So yeah you’re right, I think it’s just wording.
Thanks for posting this article. You are confirming a position we’ve had on Pub/Sub where I work. We are in the process of adding Pub/Sub to our architecture.
One thought on the priority feature in Amplify and how it relates to designing event driven system. Doesn’t that enforce bad habits? Adding a priority to your listener implicitly creates a dependency on another module in the system which Pub/Sub attempts to move you away from. My thoughts are if the system has to order the listeners for events then the events need to be de-composed further.
Hey Dan,
Yes that is very true typically. In my experience, the only good reason to order your event callbacks is for debugging purposes. I would totally agree that you are doing something wrong if the order of your callbacks affects whether or not your application works correctly because that is a clear indication that your code is not decoupled.