Prelude

Back in May I presented at DDD Melbourne on JavaScript design patterns. One of the patterns that I was talking about was the idea of pub/ sub.

Let me start by saying that this isn't the first time I have blogged about pub/ sub, it's also not the first time I'd written one, but essentially I wrote the following code snippet on stage for the audience:

(function() {
  var cache = {};

  function pub(name, args) {
    if(!cache[name]) {
      cache[name] = {
        subs = []
      }
    }
    for(var i=0, il=cache[name].subs.length; i<il; i++) {
      cache[name].subs[i].apply(null, args);
    }
  };

  function sub(name, fn) {
    if(!cache[name]) {
      cache[name] = {
        subs = []
      }
    }
    cache[name].subs.push(fn);
  };

  this.pubsub = {
    pub: pub,
    sub: sub
  };
})();

The code is short and to the point and with the addition of some error checking in it you could probably use that in a live environment.

Post-DDD

After the conference I decided that I wanted to revisit how write pub/ sub and essentially scrap the library I last wrote and write it again (for the record I know there are plenty of existing JavaScript pub/ sub libraries out there already, but mine will be cooler, just read on :P).

But something else I wanted to do, and use this project as a sandbox for it, was learn more about Coffeescript. If you haven't heard about Coffeescript but are doing a lot of JavaScript then I suggest you give it a look. Essentially it's a language on top of JavaScript which aims to remove some of the syntax guff that exists, turning JavaScript into a language that is very similar to Ruby.

So I decided to start a new project, this would be called [Postman][4] that handles sending and receiving messages.

Hello Mr Postman

So Postman is available on my github repository and at the moment I'm quite happy with its feature set. Like a good pub/ sub library you can send and receive messages, like so:

postman.receive('some-message', function(args) {
  //handle 'some-message'
});

postman.deliver('some-message', ['foo', 'bar']);

So with the receive method you can add your handlers (sub), and with your deliver method will fire messages (pub). So yes, nothing different to your standard pub/ sub except for the fact that it has a quirky syntax.

Postman also has the ability to change methods, every method returns the Postman so he can chain up his operations:

postman.deliver('message1').deliver('message2');

Making the Postman smart

Remember that I said Postman was going to be the coolest pub/ sub library and you should use it above all others? Well there is actual a feature that I have included that I haven't seen in many other libraries and that's the idea of a message bus.

Message Bus 101

The idea of turning a pub/ sub into a message bus came to me in a recent project at work where we were using pub/ sub quite extensively but we have a bit of an issue, we couldn't ensure that the subscriptions were happening before the publishing was done. This meant that we could have components on our page not receiving the messages and this can be a real issue for us.

With a message bus though we actually track all the messages that were previously published and when a subscriber attaches its callback function it will receive the previously published messages.

That means that we can do the following:

postman.deliver('message');
//some other code
postman.receive('message', function() {
  console.log('message was received');
});

How it works

Internally what Postman does is tracks every deliver method call and the arguments provided to it and when ever a receive call happens it will iterate through the delivery history and then call the callback with each of the history point.

Usefulness of a Message Buss

So now that you've seen that with Postman we can not only publish and receive messages using a known order of execution you can see that we're also allowing an unknown order of execution to happen and our messages are still going to end up at the required destination.

Told you it would be cool ;).

Something else that Postman exposes from its API is a way to get rid of messages. If you're building a long-running JavaScript application you may find a point where messages reside in the history much longer than you'd like them to, and future subscribers might not care about the state of the application back when the messages first were published. It's also important for memory management, you don't want large JavaScript objects sitting in memory if you don't really need them there. So what Postman does is exposes a method called dropMessages and we can use the method like so:

postman.dropMessages('some-message');

Postman will then remove all the call history for the passed in message name. As cool as this is it might be important to drop messages conditionally. To do this Postman allows you to not just pass in a message name, but a criteria which determines the messages to drop.

The criteria that you pass in can be either a function or a date, so you can determine which messages to drop using logic (say if you wanted to drop based on the args that it received) or drop messages older than a certain date.

If you use a function to drop messages it'll take a callback that internally get's passed to the [Array.map][7] function, so make sure that you implement it to take those arguments, with the element value being a JavaScript object matching the following schema:

{
  args: [],
  created: Date,
  lastPublished: Date
}

The args property is the arguments that passed into the message (an empty array if you don't provide arguments), created being a date object which is when the message was raised and lastPublished was when the message was last sent to the a callback.

Lastly when you call the receive method you can pass in an optional third argument which indicates whether the history will be ignored or not. By default history wont be ignored but if you're doing a subscription that you don't want the history provided to it you can use it like so:

postman.receive('some-message', function() { }, true);

Tests

Something else I decided to do with Postman was to ensure that it does actually work as advertised. To do this I've gone about writing a test suite which you can find here. A lot of people neglect testing when writing JavAScript, but I think it's quite important to at the very lest sanity check your own API. I've used Qunit and it's super simple to write out the tests. Fire up the html in the browser and you'll see just how the tests themselves pass ;).

Node.js

The final goal of Postman was to be entirely unreliant on the DOM so that you could run it in a server-side JavaScript implementation such as Node.js. I'll try and get this popped up on npm so if you want to use it in your Node projects it'll be nice and easy.

Conclusion

So this wraps up my introduction to Postman. As I said pub/ sub is a pattern that is done to death but I hope that this library has a few features which make you choose it over the myriad of existing pub/ sub libraries out there ;).

blog comments powered by Disqus