The Responsibilities Of The Various Pieces Of Backbone.js

In my last post on Backbone, I offered my opinion on why I don’t look at Backbone as an MVC framework. I left off with a statement about forgetting the MV* family for a moment, and focusing on what’s really important: how the pieces of Backbone fit together to help us create better JavaScript application. What this really comes down is responsibility. What are each of the pieces of Backbone really responsible for?

Backbone.History

The primary responsibility of the History object is managing the browser’s navigation history, which facilitates the forward and back buttons and more.

This is one of the little-known pieces of Backbone as it sits behind the scenes 99% of the time. Typically, the only time you see it come in to play is when you have a Router and you call the history object to kick off the browser’s history manipulation:

Backbone.history.start();

In truth, the History object does most of the work that you think a Router does. It facilitates the browser’s forward and back buttons by manipulating the browser’s history. It updates the URL’s route (either hash fragment or pushState) and it monitors changes in the URL’s route. It basically does everything that needs to be done with history, so that we can define and use one or more Routers in our application.

Backbone.Router And Routes

A router’s primary responsibility is to organize the route definitions and callback methods into logical groupings (typically based on related objects or related functionality). In reality, the Router organizes an app’s code for doing this but actually making the update to the route and paying attention to the route happen through the History object. Hence, the Router is used to organize our code and facilitate history manipulation and response, but not to directly manipulate or manage history.

A Router has a few secondary responsibilities, as well: 1) tokenizing the application’s state in to the browser’s URL, and 2) rehydrating an application based on a token found in the browser’s URL.

On a related not: A route is the tokenized representation of our application’s state.

Tokenizing State:

As our application progresses, it moves through various states. Some of those states can be represented by simple tokens. For example, viewing an email in GMail can be represented with a token like this:

Screen Shot 2011 12 23 at 2 24 45 PM

This token is a bookmark-able state that the application transitioned into. When I hit refresh on my browser window, GMail will display the same email message to me that I saw when I clicked on the mail item originally.

When our Backbone applications reach various states, such as viewing an email, we use a router to update the browser’s URL. We do that through `router.navigate`:

myRouter.navigate("some/route");

Rehydrating From A Tokenized State:

A router responds to changes in the route and calls the least amount of code possible to put our application in to the state we need. We do this through the use of the router’s callback methods (or events):

MyRouter = Backbone.Router.extend({
  routes: {
    "some/route": "someMethod"
  },

  someMethod: function(){
    // do stuff here
  }
});

But the router is not in control the application, the application’s state, or anything else. It does not control the views or models. A router simply takes the route and figures out which part of the application to call.

Think about a Rails or ASP.NET MVC router. Would you have the router in either of those generate some HTML and send it back to the browser? Not a chance! You would use a controller and view for that. In the same way, we shouldn’t use a backbone router to “control” our application. Rather, we should let the router figure out which route callback needs to be fired, and from there we should call some part of our application that can be in control of the application state.

Sometimes a route callback is a 1-liner that calls out to another objects. Sometimes it is 2 or 3 lines to find an item by id and then call out to another part of our app to get things running. But a router should never instantiate a view and manipulate the DOM directly with jQuery or placing the view in to the DOM. That’s the responsibility of the application and it’s state, and a route callback is only one possible entry point into the application’s state.

Backbone.Model And Backbone.Collection

Models and collections have one primary responsibility: maintaining application state. They do this by remaining in memory, with all of their associated data remaining in tact, until we change that data or de-reference the object allowing it to be cleaned up. They also do this by managing persistence with a back-end server, when needed.

In-Memory State

A stateful object is one that lives in memory with a set of specified data, even after we’re done calling methods on it. A backbone model and collection are typically stateful objects because we hold references to them in our app. This keeps them in memory and allows us to read and change the state of the object. Changing the state of an object may change the state of the overall application.

myModel.set({someAttr: "some value"});

// ... later in the code ...

alert(myModel.get("someAttr")); // => alerts "some value"

Persistent State

When the state changes, it sticks around in that state until we explicitly change it again. But the state of a model or collection in Backbone can be rather volatile. If a user leaves our application, closes their browser or hits the refresh button on the browser, the state of our application may be lost. If we need to maintain the application state between sessions of our app, we have to persist the state somewhere. We do this by calling various persistence methods on our models and collections:

// save changes to the model, back to a server
myModel.save();

// get all of the models for this collection, from a server
myCollection.fetch();

Backbone.Sync

Backbone.Sync is responsible for the network communications of the persistence operations found in models and collections. It does this by delegating to jQuery’s $.ajax method, and by default will communicate using JSON data over a RESTful API on a server.

You typically don’t touch or notice Backbone.Sync because it’s hidden behind the model and collection APIs for persistence. When Backbone’s persistence mechanisms don’t fit your needs, though, Backbone.Sync is where you want to change things to match what your back-end server requires. There are many different implementations of Backbone.Sync, including some for non-RESTful server, for use with Socket.io, and much much more.

Backbone.View

The primary responsibility of a view is to coordinate interactions between the end user and the application’s services.

The interactions between the user and the application’s services are facilitated through many different means, including the use of jQuery or Zepto.js to handle DOM level events, calling in to models and collections directly through the user of `this.model` and `this.collection` respectively, and generating new HTML that will be displayed to the end-user in response to changes in the application state.

MyView = Backbone.View.extend({
  events: {
    "click #someElement": "someMethod"
  },

  someMethod: function(){
    alert("you clicked it!");
  },

  render: function(){
    $(this.el).html("<button id='someElement' value='click me'></button>");
  }
});

In spite of their importance and having their fingers in all the pies of Backbone, views are not in control. They respond to changes in the application state in order to render the right HTML at the right time. They also facilitate changing the state by calling other objects that can change the state, but only on behalf of the user who initiates a state change by interacting with the DOM. Views are effectively the middle-man of a Backbone app.

Of course views can be used in some very simple scenarios where they respond to, manipulate, and maintain the state of an application. But this should only be done when you’re using a Backbone view to help organize some jQuery code without building a full Backbone application.

Backbone.Events

The primary responsibility of Backbone.Events is to decouple the knowledge of state changes from the response to those state changes, through the use of the observer pattern.

Backbone.Events is the little powerhouse that facilitates nearly every aspect of a Backbone application. This is the one piece of Backbone that is found in every other piece of Backbone. Every time you call “bind” or “trigger” on any Backbone object, you’re using Backbone.Events.

Could not embed GitHub Gist 152209: Not Found

Events let us decouple the various pieces of our app while still providing a unified way for all of those pieces to communicate. Aside from being used in every other piece of Backbone, we can also use Backbone.Events in our own objects. One of my personal favorite ways to do this is through the use of an event aggregator to decouple and facilitate communication between higher level application concerns.

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

vent.bind("foo", function(){
  alert("bar");
});


// ... later in the code
vent.trigger("foo"); // => alerts "bar"

Your Code

Understanding what each of these pieces is responsible for will help guide our decisions in how we use them. Can I put this jQuery selector and DOM manipulation directly in my model? Sure, I can. Should I? Heh – no. A clear separation between DOM manipulation and data manipulation is important. Separating the other concerns of the application are equally as important, of course.

Here’s the kicker, though. You’ll notice that none of what I’ve talked about in any of these responsibilities includes workflow, large scale structure, managing dependencies or any of a number of other subjects. There’s a lot that Backbone gives us, and it’s a great tool set to own. But beyond a specific set of tools that should be used to facilitate a specific set of responsibilities in our applications, you still need to know how to write good JavaScript. And you still need to write your own code to handle the rest of your applications’ needs.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in Backbone, Javascript. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://tbranyen.com/ Tim Branyen

    “But a router should never instantiate a view and manipulate the DOM directly with jQuery or placing the view in to the DOM.”
    Do you mind justifying this? This seems incredibly prescriptive and goes against the ideals of having the fragment serve as application state.

    • http://mutedsolutions.com Derick Bailey

      The fragment is not the actual application state. It’s a representation of the application state. That means we need code that can translate / deserialize / rehydrate from the representation in to the actual state. The process of rehydrating the state from a token is something that should be well encapsulated.

      These two examples are functionally the same:

      example 1)

      Backbone.Router.extend({
        routes: {
          “foo”: “showFoo”
        },

        showFoo: function(){
          var fooView = new FooView();
          fooView.render();
          $(“#someEl”).html(fooView.el);
        }
      });

      example 2)

      Backbone.Router.extend({
        routes: {
          “foo”: “showFoo”
        },

        showFoo: function(){
          FooApp.show();
        }
      });

      FooApp = {
        show: function(){
          var fooView = new FooView();
          fooView.render();
          $(“#someEl”).html(fooView.el);
        }
      }

      The difference between the two is coupling, cohesion, encapsulation, separation of concerns and the single responsibility principle. If I want to change how the FooApp shows the application’s views and make things work, I shouldn’t have to change the Router. These two concerns should be separated. I should have “one reason to change” for the router and for the FooApp (high level application object). 

      For a small demo app, #1 is ok. Anything more than 1 or 2 views, though, and the code becomes unmaintainable quickly. Having to sift through all of the cruft of doing the actual display so that you can see what the router is doing as a whole is a really bad idea in my experience. 

      For a larger example of this at play, check out my BBCloneMail sample project: http://github.com/derickbailey/bbclonemail – look at the BBCloneMail.Router.js file.

      • http://tbranyen.com/ Tim Branyen

        What you’re saying is spot-on, but I feel your approach is flawed.  Anything you assemble inside a route callback is accessible just like your FooApp.show.  Instead it would be router.show.  It seems you’re introducing new structure when Backbone already provides it.

        I fail to see how having a larger app would make this any more complicated to deal with.  Since it boils down to functions, and the route callback already is one.

        • http://mutedsolutions.com Derick Bailey

          It’s an approach that serves me well and helps me keep my code clean and organized. 

          It doesn’t always look like this, though. There are times when I’ll have a router callback look up an item in a collection and pass that item somewhere to be used, or do some other small thing like that. 

          I’m not sure I can say much else in a stream of comments… maybe another blog post… if you’re ever down in the Austin, TX area, a few beers would be better. :)

          • Ran Davidovitz

            Have you had a chance to look at BackBone, i started using it and i was impressed from the reduce of code i had shown
            it was a bit weird, i felt like i am asking my self all the time if i am writing too little code

            The main challenge when moving to BackBone is to to make sure you still write some small pieces of code that will orchestrate your views (Although they will mainly be templates)

        • Rick

          Here’s the key quote from the post: “… a route callback is only one possible entry point into the application’s state.”

          For an application only driven by user events and url history it’s fine for the router to be the controller. But when other events may drive the app – say, events pushed from other clients, or a presentation or workflow replayed – separate controllers are essential.

  • Craig Everett Jones

    Hey Derick, just wanted to say thanks for your invaluable contributions on the web. Although your entry is over 9 months old as of this comment, it’s still the most helpful and relevant discussion of backbone I’ve been able to find (as are several of your other entries). FYI I’m coming from a C# and Java background moving into HTML5+Phonegap for mobile+desktop; played with ember.js, angular.js etc. and decided I had to go back to basics and just build what I need on top of backbone.js. Thanks for being awesome.

  • gihrig

    FYI: Could not embed GitHub Gist 152209: Not Found