This is part 3 of a series. Parts 1 and 2 can be found here:

In parts 1 and 2, we looked at how a client-side message bus can help you decouple the components you use in a web application, enabling better testability & extensibility.  Now I’d like to look at a few common client-side messaging anti-patterns that I’ve run across (and…gasp…been guilty of).

1.) Mutating the Message

In a language like Erlang, the message passing style is said to be “immutable” – really meaning that the message sent is a copy, so that there is no shared mutable state between sender and recipient.  The challenge in JavaScript is that pub/sub message content is never a deep-copy-per-recipient (unless your pub/sub library enforces that….and many developers would rightfully balk at the overhead that deep-extending on each publish would entail as well as the constraints of what could be published).  This unfortunately leaves open the temptation to allow one of your subscribers to modify the message content – and since the subscribers initially share a reference to the same copy of the message, this can be a recipe for disaster.  Let’s look at an example:

var moduleA = {
    someOperation: function(itemToWorkOn, settings) {
        bus.publish("mySettings", settings);
        bus.publish("workItem", itemToWorkOn);
        appComponent.DoWork(settings, itemToWorkOn);

// elsewhere in the app
bus.subscribe("mySettings", function(settings) {
    settings.someValue = (environment.lookThisUp) ? "OptionB" : settings.someValue;
    settings.subOptions = $.extend(true, app.defaults, settings.subOptions);
    // and all kinds of other message-mutating operations

// and yet again, elsewhere in the app
bus.subscribe("workItem", function(item) {
    if(item instanceof SomeType) {
        var stuff = item.oldField.split(' ');
        item.newFieldA = stuff[0];
        item.newFieldB = stuff[1];
        delete item.oldField; // ORLY?!
    else {
        // more mutating atrocities here....

// Oh hai, brand new subscriber...too bad it's going to fail
bus.subscribe("workItem", function(item) {
    $("#someElem").html("Currently Processing: " + item.oldField); // BUT Y U NO GIVE ME OLDFIELD?!

Now imagine that these snippets exist in different files… a large application…with multiple developers actively updating the codebase.  Have fun finding out why the brand new subscriber you added can’t access the “item.oldField” value on the message.

2.) Relying on Subscriber Priority for Critical Operations

The above anti-pattern (mutating the message) tends to lead naturally into this one.  If you’re allowing your subscribers to alter shared message content, then the order in which your subscribers receive messages becomes critical.  If you continue along these lines, and allow more than one subscriber to alter the message content, and downstream subscribers depend on upstream changes when they mutate the message, then you have no choice but to rule subscription priority with an iron fist.  “But that’s what susbcription priority is for!  Isn’t that what guaranteed order is about?”  First, guaranteed order is usually referring to the order in which multiple messages are delivered to one or more clients, not one message to multiple recipients.  Second, while I don’t have any problem with a pub/sub library supporting priority (after all, postal.js has it), I do think it’s a code smell if your application depends on subscriber priority for critical operations.  That tells me right away that your app is either a.) allowing subscribers to mutate the message, or b.) improperly handling immediate vs deferred publication of messages.  The brittle nature of this approach accelerates as new susbcribers are introduced (or new use-cases cause affected parts of the app to change).  It only takes one susbcriber to mess up the chain-of-dependent-message-mutations (or one developer to introduce a subscriber with the wrong priority).  This violates SRP & SoC on so many levels!  A wiser approach is to develop the application in such a way that subscribers treat the message as an immutable object.  If they need to alter it’s content for their own internal operations, then copy or extend the data onto something local so they are not dealing with shared references any longer.  If you’re using a pub/sub system to run a “transformation pipeline”, and you intend for each subscriber to alter the message content, my suggestion would be to try & use something lighter – like an array of functions that get called in order, each returning it’s transformed data for the next to consume.  On the other hand, using priority for non-critical operations is not only reasonable, but often times a very powerful tool (for example) in making the UI respond to changes in a particular way so the user doesn’t wonder if their input is being processed.  Just remember, any time you find yourself saying “Well, the only way that would work is if this subscriber has a priority of 17…“, then I can guarantee you there are better ways to solve the issue that will save you (your team and your app) from many headaches down the road.

3.) Too Much Of a Good Thing

While messaging is a powerful tool in decoupling, like anything, it can be overused.  For example, using messaging for internal events within a component – especially if it’s on a publicly accessible channel - is probably overkill.  When you’re communicating between ‘components’, though, messaging is a great fit.  For example, Backbone provides nice eventing between model and view.  Using a message bus like postal just to have a view’s model notify it of changes is a bit much!  However, using postal (or even your own custom pub/sub using Backbone’s events module) to handle communication between separate views (or models) enables your app to be free of tightly coupling all the disparate components that need to know about events external to themselves.  When I encounter a developer who’s extremely leary of pub/sub in the browser, it’s almost always because they’ve seen it overused in this way (coupled with the next anti-pattern below).

4.) Improper Abstraction of Subscribe and Publish Calls

This is a tough one.  As a general rule, this anti-pattern can be guarded against by constantly asking yourself two questions:

  1. Can I move this publish or subscribe call into my app’s infrastructure?
  2. Do I have pub/sub calls scattered throughout my entire codebase – making it difficult to determine where/when/why messages are being used?

The main symptom of this problem is when you see publish and subscribe calls everywhere, with no appearance of structure/rhyme/reason.  It usually boils down to not having a good differentiation between internal events in your module vs public events of which other components should be aware.

Another way to help avoid this anti-pattern is to write your component with a good public API without any assumption that messaging will be involved, and then write an extension/wrapper that ties that API into a message bus.  I’ve taken this approach with libraries like machina.js – in part because I want to provide developers not using pub/sub a good API experience, and because it helps me concentrate the message-bus-related code in one area, rather than it being littered everywhere in the codebase.  I’ve seen developers like Alex Robson take a similar approach (see cartographer) with good success.

5.) “Tightly-decoupled”

In a lively chat session with several developers about this post, one of the things we all lamented is seeing one version or another of what I jokingly called “tightly-decoupled” modules.  Perhaps the worst version of this is when module A uses messaging on a publicly accessible channel for its internal events, and module B subscribes to those.  Unless module B is a diagnostics wiretap printing feedback to the console, dare I say, you’re doing it wrong™?  When a module uses messages for public events, the message becomes the contract, so its understandable for your consumers to need tweaking if you change your public contracts.  But if you change module A’s internal pub/sub and it breaks module B, your modules are tightly decoupled – and in need of either non pub/sub internal eventing, or a private channel/sandbox local to the module.

6.) Not Enough of a Good Thing

Just as it can be overused, it can be under-utilized.  In discussing this post with Eli Perelman, he mentioned that he is often frustrated when a library doesn’t expose enough hooks to tap into.  “Just because you may not need control at that point doesn’t mean someone else won’t”, he said.  I agree.  I had this very frustration with version 1 of Knockout.js when I wrote a template engine plugin allowing developers to pull in external templates.  At the time, Knockout had a baked-in-assumption that the template binding lookup would be synchronous (since it was expected for templates to be in script elements on the page).  I nearly abandoned the support of my plugin, until I saw that version 2 of Knockout would have a better mechanism to support asynchronous retrieval.  Alex summed it up well:

“The problem is that most libraries are written as a tightly coupled block of calls vs. components.  In the end, I should be able to use your library naively, but I should also be able to understand it’s components and consume them as micro-abstractions.  I think it’s partly missing opportunities to encapsulate and decouple appropriately so that what you have is a nice conventional way to use a set of components that are decoupled and can be recombined or customized as needed.  Messaging helps address that.”

Next time I might just quote Alex the whole time!

Tagged with:  
  • Josh Bush

    Good post! When exploring new methodologies, it’s very important to know where the limits and pitfalls are.

    #3 is always a killer for me. I find a new hammer and everything suddenly looks very nail-ish.

  • Anonymous

    I really feel that messages should be immutable objects. If you need to bastardize it, dispatch a new message.

    Great work man. I keep hearing your name everywhere with these libraries you are writing. Came up this weekend at CodePaLOUsa. You are rocking the house.

    • Jim Cowart

      Thanks Burke – it’s quite sobering to hear of other people picking some of these libs up and using them. And I definitely agree – messages should be treated as immutable. It never ends well when they aren’t!

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #1068