Backbone.js Organizational Patterns: Part II

In Part I, we established basic organizational patterns for separating Backbone.js applications into layers. In Part II, we will explore various complimentary patterns and refinements to those described in Part I.

Extension 1 – Reuse between modules with mixins

After building a few microapps, you will likely wish to share views and models between modules. The generalization and reusability of views is, in fact, one of the primary motivations for developing the controller abstraction. For instance, you may develop a generic ListView or ModalView.

Reusable behavior should be extracted into a Shared or Core module, which is distinct from other modules. Reusable functionality can take the form of base classes, like a ListView.

However, inheritance isn’t our strongest choice. Truly modular, cross-cutting behavior does not comport well with a parent/child relationships.

In the example above, I wouldn’t consider WordsView to be a subtype or specialization of ListView, because while WordsView requires list-like behavior, it is not, within our domain, conceptually a child of ListView. List-like behavior is a mechanical, incidental convergence of ListView and WordsView, not a hierarchical relationship we benefit from expressing, like how an AccountManger might be a subclass of Employee.

To avoid inheritance, create a mixin (a JavaScript object which is extended on the target class’ prototype).

The mixin approach opens up the possibility of extending multiple objects:

Now we have a modal list!

Extension 2 – Store ephemeral state on the controller

Not all application data needs to be persisted to the server. State might also be encoded into the URL, placed in local browser storage, or simply stored as ephemeral state in memory and lost upon refresh.

Scenarios for ephemeral state storage might include:

  • Whether a list is currently expanded or collapsed
  • The current search query
  • Whether sorting is enabled

In a universe of only Backbone views and models, the view would have been the logical place to store ephemeral state. However, once we introduce the controller abstraction, controllers become the more logical facility.

Avoiding ephemeral state storage in our views means that they can remain as generic as possible, and we can maximize reusability.

Models and ephemeral state In addition to controllers, it is occasionally permissible to store ephemeral state directly on models. For example, a Comment model might have an unpersisted “editing” attribute. When an admin clicks the “edit” link in a threaded list of comments, the “editing” attribute is set to true. A separate controller and view can listen to the CommentCollection for changes in the “editing” attribute, and display a modal edit form when editing is activated.

Extension 3 – The delegate pattern

Apple’s Cocoa framework uses the delegate pattern to mediate communication between the view layer and the controller layer.

View controllers are typically the delegate or data source objects for many types of framework objects. The delegating object keeps a reference to the other object—the delegate—and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled

What would this look like in Backbone?

Here is an example of familiar events based communication between the controller and the view.

To achieve the delegate pattern, we can invert dependencies such that the view accepts a controller option. The view then calls methods directly on the delegate object.

The primary advantage of the delegate pattern is that it obviates the need to subscribe to events in the controller. We can slim down our controllers and potentially elucidate control flow with explicit method calls on the delegate rather than the relative indirectness of event subscription.

One potential disadvantage to the delegate pattern is JavaScript’s dynamism. JavaScript lacks type and interface checking, so, without going to a great degree of trouble, we cannot guarantee that the delegate will implement every method the view expects. If you’re developing in Coffeescript, you will likely end up littering existential operators before delegate method calls.

But really, these two patterns are not substantively different. Either the view is calling methods on the controller delegate or Backbone.Events is calling methods on the controller. Firing an event is just an indirect version of delegation.

Extension 4 – Expanding beyond controllers

The putative goal of the controller abstraction is to absolve our views of domain logic. This does not imply, however, that our migrated domain logic must live in the controller. In fact, vesting the controller with too much domain logic is as perilous as authoring logic heavy views.

It is often said that a controller should know what to do, but not how to do it. The controller is a coordinating entity, a mediator. It delegates labor between application layers. For instance, the view layer might interpret a user action as “the comment resource should be saved”, and the controller delegates the saving procedure to a comment model.

One should be able to read controller source code and get a good sense of how the application is wired at a high level without getting bogged down in procedure.

Carving out new domain objects

Logic which is not related to persistence (model logic) or DOM manipulation (view logic) should reside in a plain-old JavaScript domain objects (POJOs).

Consider the resume ensmartening module from Part I. The module will likely include a Resume model to store resume attributes such as “text” and “date.”

We could give Resume an ensmarten method, which returns a new and improved Resume instance, but ensmartening is a sophisticated process, and it transcends the core responsibilities of Resume: persisting and retrieving data. As such, Resume is not a good home for ensmarten. Similarly, the controller, which is not supposed to house procedural code, is also not a good home for ensmarten.

Our best course of action is to build a POJO dedicated to resume improvement. This new object acts as a service which composes lower level Resume models.

The ResumeEnsmartener is invoked from the controller.

It is vital that the controller layer stays relatively thin and delegates work to domain objects with simple, declarative interfaces. Domain objects can take the form of abstract models, services, policy classes, data repositories, etc., and are ideal for coordinating lower level entities like Backbone models.

Extension 5 – Using an app namespace as a messaging bus

On occasion, unrelated module components require a means of communication. One approach to solving this problem is to enable event publishing and subscribing on the module itself.

Analytics is a reasonable use case for this sort of thing. Many heterogeneous controllers might want to publish their activity, and it is the prerogative of a separate service to choose which published controller events to translate into analytics.

For example, in the thesaurus app, the search controller originally emitted analytics events like this:

This approach requires that we pass the analytics service to every controller, and that specific controllers create analytics events when appropriate. To gain high level insight into which analytics we’re tracking, we’d need to dive into each controller and search for @analytics.trackEvent references.

Let’s refactor analytics tracking out of the controller. The first step is to refactor the controller to trigger events on the messaging bus, the Thesaurus module.

The Thesaurus analytics service now translates module events into analytics:

Or, for extra credit, we can simplify the implementation a bit by partially applying trackEvent arguments.

A word of caution

Use the global messaging bus pattern sparingly, and reserve it for events which are truly global in nature within the context of your app. It is possible, but thoroughly inadvisable, to wire the entire application together via global events. Such abuse of the pattern makes it very difficult to discern object dependencies, and dissolves the structure of the app.

Extension 6 – Marionette

This wouldn’t be a Backbone.js blog post without at least one obligatory “go check out Marionette” solicitation. Marionette expands the Backbone universe, and includes a module system, events aggregation, controllers and more.

A Parting, Shameless Plug

Do you like mucking around with JavaScript and/or Ruby? Do you like discussing application design? We’re usually hiring developers!

Tweet at Foraker

Share this post!