Skip to content

Message Bus vs "Pub Sub"

Piotr Kowalski edited this page Feb 13, 2016 · 7 revisions

JavaScript developers often throw around the term "pub/sub" when referring to a number of "observer/observable" patterns. Quite technically, anything that "publishes" data that other components are interested in is a "publisher" and the listeners are "subscribers". However, it's critical that developers understand that not all "pub/sub" approaches are interchangeable, and some work better in certain situations than others. A message bus is a very specific form of "pub/sub", and its implementation details will have a very different kind of impact on an application than if standard event delegation were used in its place.

Consider the following approaches:

// over-simplified event delegation/broadcasting
var publisher = {
    subscribers: [],
    publish: function() {
        var args = [].slice.call(arguments, 0);
        _.each(this.subscribers, function( subscriber ) {
            subscriber.apply(this, args);
        });
    }
}
publisher.publish("George", "Washington", { country: "United States", year: 1791 } );

// over-simplified message bus
var msgBus = {
    subscribers: [],
    publish: function( data ) {
        _.each(this.subscribers, function( subscriber ) {
            subscriber.call(this, data);
        });
    }
}
msgBus.publish({
    firstName: "George",
    lastName: "Washington",
    otherData: {
        country: "United States",
        year: 1791
    }
});

The first approach more closely follows the paradigm of event delegation. Subscribers add themselves to the queue and expect a specific argument signature to be passed into their callbacks when the event is invoked.

The second approach follows a "message bus" paradigm, where the subscribers expect a message body (or sometimes a full envelope). The envelope is consistent for every publish - meaning that the subscriber callbacks all follow a common & consistent convention - yet the body of the data can vary as much as a publisher wants it to.

Both approaches have strengths, though postal has staked itself on a design opinion that the second approach offers more flexibility with fewer drawbacks (for example, your subscribers aren't constantly changing method signatures, and it's possible to decorate a message envelope with metadata describing more about the message than just the data in the message body). For internal events (within a module), the Observer pattern is a great fit. When it comes to communications between modules, a message bus, with a standard envelope and channels to segment topics, offers much more power and flexibility.

By "modules", I mean discrete/cohesive components. This might be an AMD or CommonJS module, or a "view" or a "view model" in an MVC framework, etc.

postal.js strongly adheres to the idea that a consistent 'envelope pattern' is essential. Every postal message is structured with along these lines:

{
    channel: "ChannelName",
    topic: "Topic.String",
    timeStamp: "2012-05-22T16:59:55.009Z",
    data: {
        firstName: "James",
        lastName: "Madison"
    }
}

Publishers can decorate the envelope with additional metadata - for example, a "replyTo" topic can be included, or a correlationId (which would tie several messages on a given channel/topic together as related).

When you call publish from a ChannelDefinition instance in postal, the channel, topic and timeStamp values are added for you automatically (though it is possible to override the topic in a ChannelDefinition publish). You cannot override the channel or timeStamp values when publishing from a ChannelDefinition.

When you call postal.publish, you must provide the channel and topic in addition to the data being published, but you cannot override the timeStamp value (it's added to the envelope internally by postal).

Clone this wiki locally