BeauLebens.com

An aggregation of Beau on the internet

Menu

Skip to content
  • Blog
  • Archive
    • Posts
      • Tweets
    • Images
      • Flickr
      • Instagram
    • Links
      • Delicious
      • Instapaper
    • Places
      • Check-ins
      • Trips
  • Explore
  • Projects

Decoupling Backbone.js Applications with pub/sub – Safari Books Online’s Official Blog

http://blog.safaribooksonline.com/2013/10/02/decoupling-backbone-applications-with-pubsub/
  • #read

Decoupling Backbone.js Applications with pub/sub – Safari Books Online’s Official Blog

A guest post by Tim Ruffles, a Ruby & JavaScript developer based in London who teaches and mentors developers, and works on SidekickJS, a code-quality tracker for teams. Previously he was a front-end tech-lead for Skimlinks and Picklive; he talks about Javascript at conferences and events and can be reached at @timruffles.

Backbone.js applications can become tightly coupled, however, the publish/subscribe (pub/sub) pattern is a great way to decouple these applications, and it is easy to implement the pub/sub pattern in Backbone.js.

In this post, we will look at how pub/sub makes it easier to change, test and reuse your Backbone.js code by reducing coupling. We’ll also see how it affects the structure of your application – especially making sure your Router doesn’t become an all knowing “God object.” You can also take a look at a post on persistence in Backbone.js that details how to split your model from persistence in Backbone.js.

The problem

First, let’s talk more about the problem this pub/sub pattern solves: coupling. Coupling refers to the knowledge two parts of your code, or components, have of each other. You know two components are coupled when a change to one requires a change in the other. Coupling normally takes the form of one object accessing methods or properties on another. The problem with coupling is that if an object’s methods change, or a new object replaces it, you’ll need to update every part of your application that uses it.

Pub/sub reduces coupling, because rather than talking to other objects directly, you can publish messages on a shared pub/sub object. In Backbone.js, an example might be to play a sound on a user being logged out:

1
this.listenTo(this.pubSub,“user:loggedOut”,this.play.bind(this,“bye”));

Notice we don’t know how the user was logged out, or which object was responsible, only that it happened. As you can see, the pub/sub is coupled to the name and data payload of a message. The only dependency is on the pub/sub object that has a very small API – you can send or receive messages. Which component triggers the message ceases to matter – you just ensure your system reacts to the vocabulary of events correctly.

Let’s see some code

Let’s look at a concrete example. We’re building a little tool to search your favorite tweets. You have a search box, a history view, and a results box. You can search by text, jump back to previous searches, or filter by a specific author.

Our pub/sub implementation is very simple: we just mix Backbone.Events into an empty object:

1
var pubSub = _.extend({},Backbone.Events);

We’ll just use the standard trigger and on methods to publish and subscribe to messages.

Using our pub/sub implementation we can easily make the views completely unaware of how the search process occurs. They just publish a event with a new query criteria, and a different object is responsible for creating a new search from it. If it triggers a new search, that is broadcast, and any views that are responsible for displaying the search are updated.

For instance, the search view simply triggers a criteria change on submit:

1
2
3
4
5
6
7
8
9
10
var SearchView = View.extend({
events: {
“submit”: “publish”,
},
publish: function() {
this.pubSub.trigger(“search:criteria”,{
retweets: this.$(“.retweets”).val()
});
}
});

Our TweetList listens for a new search, and sets up listeners and re-renders once it hears it:

1
2
3
4
5
6
7
8
9
10
11
var TweetsList = View.extend({
initialize: function() {
this.listenTo(this.pubSub,“search:set”,this.setupSearch,this);
},
setupSearch: function(search) {
this.stopListening(this.tweets);
this.tweets = search.get(“tweets”);
this.listenTo(this.tweets,“reset”,this.render,this);
this.render();
}
});

Allowing users to switch back and forth between searches in this design is simple. The history view simply fires search changes, and we have a plain old JavaScript object to manage the process of transitioning between searches when new criteria is supplied:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var HistoryViewItem = View.extend({
events: {
“click .search”: “switchSearch”
},
switchSearch: function() {
this.pubSub.trigger(“search:set”,this.model);
}
});
var SearchManager = function(pubSub) {
this.search = new TweetSearch;
this.searches = new TweetSearch.Collection;
this.searches.fetch(); // perhaps from local storage etc
pubSub.on(“search:criteria”,function(change) {
var criteria = _.defaults(change,this.search);
var search = this.searches.where(criteria);
if(!search) {
search = new TweetSearch(criteria);
}
pubSub.trigger(“search:set”,search);
this.search = search;
},this);
}

So, we can see that none of these objects know about any one of the others. Their behavior can all be considered in isolation. If they do the right thing given the events firing, the system will work as a whole.

Decoupling

If we contrast this approach with a more traditional one, we can see where the decoupling occurs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var SearchView = View.extend({
initialize: function() {
this.searchManager = this.options.searchManager;
},
publish: function() {
this.searchManager.updateCriteria({
retweets: this.$(“.retweets”).val()
});
}
});
var TopLevelView = View.extend({
initialize: function() {
this.searchView = new SearchView({searchManager: this.options.searchManager})
}
})

We now have two possible problems when refactoring occurs. First, the search manager’s API is exposed. Changes to this API will affect all components that were updating the criteria. Secondly, we have to pass around our search manager through layers of hierarchy that don’t need it themselves.

In testing, we now also need to pass in a custom mock or an instance of our search manager. If we change the API, that’s one more place our coupling has forced us to change our code. With the pubSub implementation, we can completely change how the system responds to messages without a single line of application or test code changed in the components that broadcast them.

Compared to events

Pub/sub can see similar to events. The core difference is that you don’t know who’s firing the events, and you don’t need a reference to them to listen. Objects in a pub/sub system share a central pub/sub object that they fire messages on. The listener doesn’t have to know about the publisher to listen!

So, from our comparison, we can see one immediate advantage is the non-locality of handler. With events fired via the Backbone.Events methods that are present on Backbone.View, only objects with a reference to that view can listen. For instance: let’s say we refactor a child view that fires a “search:set” event to have a child view of its own that now is responsible for firing the event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var ParentView = Backbone.View.extend({
initialize: function() {
this.childView.on(“search:set”,this.someHandler,this);
}
});
var ChildView = Backbone.View.extend({
initialize: function() {
this.newChildView.on(“search:set”,function(search) {
this.trigger(“search:set”,search)
},this);
}
});
var NewChildView = Backbone.View.extend({
// fires search:set events
});

It’s clear we’ve had to listen to the new child view to ensure that the “search:set” event is still visible to our ParentView. Pub/sub doesn’t have these problems, since the pub/sub object is shared throughout:

1
2
3
4
5
6
7
var ParentViewWithPubSub = Backbone.View.extend({
initialize: function() {
// no reference to the object firing – regardless how many levels of hierarchy
// we’ll have no additional work to do for the event to still get to us
this.listenTo(this.pubSub,“search:set”,this.someHandler,this);
}
});

Getting access to the pubSub object

One potential question that arises is how to get the pub/sub object into our views. One answer is to pass it via constructors, so each view passes it down into its child views. This creates a lot of boiler-plate.

1
2
3
4
5
6
var TopLevelView = View.extend({
initialize: function() {
// needs to happen for every child view, in every layer of hierarchy
this.childView = new ChildView({pubSub: this.options.pubSub})
}
});

A better approach is to embrace Javascript’s dynamism. We can set the pubSub object on our View’s prototype, so it’s shared among all instances. To maintain testability we create a View subclass that accepts a pubSub in its options, and falls back to first the prototype, and then a global pubSub object.

1
2
3
4
5
6
var View = Backbone.View.extend({
constructor: function() {
Backbone.View.apply(this,arguments);
this.pubSub = this.options.pubSub || this.pubSub || window.pubSub;
}
});

This has all of the ease of a global pubSub object with none of the pain: we keep testability, and we still have the option to make multiple pubSub objects if we need to isolate a module.

1
2
3
var subSystemPubSub = new PubSub;
var usesLocalPubSub = new View({pubSub: subSystemPubSub});
var alsoUsesLocalPubSub = new View({pubSub: subSystemPubSub});

Where to use pub/sub

I use pub/sub in my views. I don’t think it makes sense to use pub/sub in model code, as the views (which do double duty as controllers in Backbone.js) are responsible for making sure models are updated in response to user commands. Models sit and handle domain logic, and controllers can listen to and publish these changes as events that need to change our views.

So: publish model events from controllers that are listening to models if required, but don’t let models use pub/sub internally – it mixes up view concerns with your domain.

Testing pub/sub code

Testing code that uses this pattern is much easier – you simply make sure components respond correctly to messages, and fire any broadcasts it should. We never have to setup complex collaborator objects because we always have just one: our pub/sub.

1
2
3
4
5
6
7
8
it(“triggers search criteria on submit”,function() {
var mockPubSub = sinon.mock(new PubSub);
mock.expects(“search:criteria”).once();
var searchView = new SearchView({pubSub: mockPub);
searchView.$(“.search”).val(“backbone”);
mock.verify();
});

Pub/sub and Backbone.Router

Pub/sub also helps to reduce a system’s coupling to the router. A typical anti-pattern I see in Backbone.js code is the ‘God router,’ where the router knows about every model and view. This is a nightmare to test, maintain and debug, since the router grows with every new feature. For me, the router should simply be thought of as another View, firing events on the user’s interaction with the URL. This aligns with the “small components, each doing one thing well” approach you may have heard referred to as the Unix philosophy. A router should respond to URLs, not instantiate views.

When using pub/sub, I prefer to have no methods at all on the router and merely publish its events as view:change events. That way, if a view change needs to be triggered for another reason – for instance undoing a command, or a user being logged out – the rest of the application can just as easily fire the view:change event. Supporting PhoneGap and mobile websites becomes much easier this way. And finally, testing the router is made very simple as well.

Pub/sub – object orientation done right

So: if you’ve ever felt that Object Oriented code feels hard to change, and that refactoring always involves changes spreading throughout the system, I think pub/sub will help. Alan Kay’s original conception stressed messaging over the internal properties of a system’s objects. Pub/sub gets a lot closer to this ideal, and keeps your code malleable, easy to test, and simple. Enjoy!

Be sure to look at the Backbone.js resources that you can find in Safari Books Online.

Not a subscriber? Sign up for a free trial.

Safari Books Online has the content you need


Developing a Backbone.js Edge incorporates the best practices and the techniques from the combined authors’ experience of developing many Backbone.js applications. In this book you get a complete guide to Backbone.js and equip you to start using the library straight away. While writing this book the authors developed an example app, called Hubbub, to illustrate the various features of the library.


Developing Backbone.js Applications shows you how to get the job done with Backbone.js. You’ll learn how to create structured JavaScript applications, using Backbone’s own flavor of model-view-controller (MVC) architecture.


Backbone.js Cookbook contains a series of recipes that provide practical, step-by-step solutions to the problems that may occur during frontend application development using an MVC pattern. You will learn how to build Backbone.js applications using the power of popular Backbone extensions and integrating your app with different third party libraries.

About the author


Tim Ruffles is a Ruby & JavaScript developer based in London. He teaches and mentors developers, and works on SidekickJS, a code-quality tracker for teams. Previously he was a front-end tech-lead for Skimlinks and Picklive. He talks about Javascript at conferences and events and can be reached at @timruffles.

  • Why I want to get rid of managers, but why I can’t [Part 1 on flat orgs]
  • Backbone.js Value Objects


Shortlink:

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to print (Opens in new window) Print

Like this:

Like Loading...

Similar Entries

Saved on Instapaper 6:45 pm, October 13, 2013

Post navigation

← 5 quotes about the debt ceiling that will totally freak you out
Checked in at Taqueria Cancun →

People

  • Erika Schenck (1,817)
  • Helen Hou-Sandi (194)
  • Automattic (177)
  • Scott Taylor (132)
  • Kelly Hoffman (131)

Categories

  • Uncategorized (28,849)
  • Personal (9,315)
  • Posts (304)
  • Techn(ical|ology) (192)
  • Projects (77)

Tags

  • read (3,919)
  • wordpress (624)
  • sanfrancisco (421)
  • automattic (394)
  • photo (392)

Year

  • 2025 (231)
  • 2024 (1,014)
  • 2023 (953)
  • 2022 (819)
  • 2021 (906)
Powered by Homeroom for WordPress.
%d