Backbone.js Organizational Patterns: Part I

Large JavaScript frameworks increasingly predominate the web application development scene. In particular, EmberJS and AngularJS are impressive tools for efficiently authoring rich client-side apps, and the virtues of framework-based development are difficult to deny.

But frameworks are not categorically superior. The browser is a viable host for a wild diversity of applications, and the paradigm of a single framework is not likely suited to all problems. Not to mention, climbing the learning curve and discovering framework conventions is often time-consuming and frustrating.

An elegant weapon for a more civilized age

In the era of the JavaScript framework, lighter-weight libraries are not outmoded. Backbone.js is such a library.

Wonderfully small and unopinionated, Backbone is perfect for a particular class of problem: the multipage app.

Multipage apps

In a traditional web app, the client issues a request and the server returns static HTML content. Each request requires an entire page reload. In a single page app, most HTML rendering is handled client-side (i.e. within the browser), and the server is primarily an API for reading and persisting data.

A multipage app is a hybridization of a single page app and a traditional app. In a multipage app, one endures reloads between pages, but the pages themselves are enriched with JavaScript. Think of it as having many tiny, modularized single page apps within a larger, traditional application. Myriad microapps, if you will.

In pursuit of the practical

At Foraker, we find the multipage app approach practical. Creating a rich client-side experience is almost always costlier than the traditional alternative, even with the benefit of a framework. For instance, vanilla form-based CRUD functionality is trivial to build traditionally with Ruby on Rails, and the CRUD experience is not significantly diminished by full page reloads.

Building multipage apps allow us to be judicious about where we want to spend time (and money) providing compelling interactivity, and where we think traditional request/response architecture will suffice.

Backbone as a foundation

Opting for microapp development via backbone.js instead of utilizing a framework implies that the developer is responsible for establishing sensible organizational patterns and abstractions. Backbone intentionally limits the number and complexity of its abstractions, and we’re expected to fill in the gaps. From the docs:

Backbone.js aims to provide the common foundation that data-rich web applications with ambitious interfaces require — while very deliberately avoiding painting you into a corner by making any decisions that you’re better equipped to make yourself.

Attempting to cram an entire application into Backbone models and views is a common pitfall for novice backbone.js developers. Today we will examine strategies for building upon the Backbone foundation, and formulate abstractions to more effectively structure our applications.

An example illustrative archetypal application

Let’s establish requirements for a thesaurus application to help illustrate organizational patterns.

The thesaurus application will help users appear smarter on resumes. There are just two features:

  1. A search box for finding words and displaying synonyms. We’ll call this the “thesaurus” microapp.

    A mockup of the Thesaurus microapp

  2. A text area where users can paste dull resumes. The resume text will be processed, and small words will be replaced with larger, smarter-sounding words. We’ll call this the “resume ensmartening” microapp.

    A mockup of the Resume Ensmartening microapp

Initial Structure

Let’s start with the following directory structure. Don’t worry about the directory contents yet. We’ll get there. Assuming a top level JavaScript directory called “scripts”, and assuming the use of Coffeescript:

Each microapp gets its own directory and module.coffee file. This may seem like a great deal of ceremony for the simplicity of our feature set, but this exercise is all about absorbing future complexity and changes.

Note: We don’t want any top level “/backbone”, or “/collections”, “/views” and “/models” directories which generators are so fond of imposing, and we do not want a top level App namespace.

The anatomy of a module

Each microapp lives in its own module, completely isolated from all other microapps. We’ll use a rudimentary object-literal based module system.

Here is the thesaurus module in /scripts/thesaurus/module.coffee.

And here is the resume ensmartening module in /scripts/resume_ensmartener/module.coffee.

The object literal module system allows us to register objects within a namespace:

And access the objects later:

All objects related to the Thesaurus microapp will live somewhere in the Thesaurus namespace, and likewise, all objects related to the ResumeEnsmartener microapp will live in the ResumeEnsmartener namespace.

We have to go deeper

You’ve probably noticed the Thesaurus.App and ResumeEnsmartener.App classes. Before discussing these high-level App classes, let’s switch gears and momentarily dive into the Thesaurus.Views namespace, working our way back out to the App.

Right now, the Thesaurus module contains a single Backbone view: Thesaurus.Views.SearchView. The SearchView accepts user input (e.g. ‘happy’) and returns synonyms (e.g. ‘ecstatic’).

The SearchView manages 100% of the Thesaurus UI

A view in distress

Views are often the first place growing Backbone apps go awry. They slowly accrete domain logic, manage increasingly complex state, persist data, and are unpleasant to test.

The Thesaurus.Views.SearchView is an example of a view in distress. It has suffered several requirements shifts and acquired many responsibilities.

distressed view diagram

Ugh. That wasn't enjoyable.

What does this view do?

Several things:

  1. Validates the search query length
  2. Refreshes search results when the input changes
  3. Updates the URL state
  4. Emits analytics data

What should a view do?

Contrary to the design decisions evinced in SearchView, views have a pretty narrow job description:

A view should only serve to abstract away the DOM.

But what does it mean to “only abstract away the DOM”? Practically speaking, this restriction suggests a view can do two things.

1. Translate DOM activity in to semantic expressions of user intention.

The DOM is an inconsistent mess of classes, IDs and events. Some DOM entities emit “change” events, others “click” events, and through the miracle of jQuery plugins, “sort” or “drop” events. Views serve to hide the mess.

For example, a User might indicate a desire to edit a comment resource by clicking a link with the class “edit”. We identify a click via a “click” event, and the we identify the edit link by targeting the “edit” class.

The view should translate this DOM click event into a user intention by triggering a semantic "comment:edit" event. The rest of the application just needs to know about this “intention to edit” event, and not the gory link-clicking details.

2. Represent data as HTML.

The view should serialize data into an HTML representation. For example, SearchView represents search results as <li> elements within a <ul> element. Effectively, the view renders a human parsable veneer around data.

Repairing SearchView

Here’s a refactored version of SearchView which is consistent with the above principles.

SearchView has been reduced to one simple task: communicate the user’s intention to search. It does not validate input, it does not perform the search, it does not emit analytics or update the URL state.

So where did all our logic go? If the view doesn’t execute the search or track analytics, what does?

Backbone controllers

Once DOM events are translated into user intentions, a controller acts on user intentions. Let’s introduce a controller for the SearchView.

The controller has taken over the query length validation, URL updating and analytics responsibilities, but engenders new questions. Where did the injected options come from? And where are we handling displaying the search results? Our refactor isn’t much of an improvement if we’ve thrown away functionality…

The answers lie in Thesaurus.App.

The App

The App is charged with building dependencies and instantiating the models, controllers and views with their respective dependencies.

We can start our microapp on whatever page requires JavaScript enrichment.

Reading through Thesaurus.App isn’t much fun; it’s rather dense. Luckily, we needn’t read through too carefully. The App is almost more of a configuration, wiring together infrastructural pieces with very little behavior of its own.

Leveraging the data layer to gain independence

We can now answer the question of “what happened to rendering search results?”. Rendering of results is extracted into its own ResultsController. The controller listens for “add” or “remove” events on the results collection and tells the view to rerender, and is never aware of the search process. The tasks of collecting a search query and rendering results are now independent.

Separating searching (i.e. the generation of results data) from results rendering is an example of leveraging the data layer, a key technique for pulling apart a convoluted Backbone application. The data layer is a single source of truth, and is shared amongst all controllers and views. Data updated in one controller is displayed by another, without either controller having to know about the other.

The UI has been broken down into two independent subsections.

A quick review

To review, a microapp is comprised of the following components, or layers:

  • Modules e.g. Thesaurus
    • Provide a namespace for the microapp.
  • App objects
    • Instantiate collections, models and views
    • Inject dependencies and wire objects together at a high level
  • Controllers
    • Mediate between user intentions and the data layer
  • Views
    • Wrap UI and translate DOM events into user intentions
    • Represent data as HTML
  • Data
    • Backbone collections and models which persist application state
    • A ‘single source of truth’ shared by many controllers and views

The following diagram demonstrates how the components fit together:

component diagram

But why structure an application this way?

Layers are important

We should endeavor to stratify applications into layers and define strict boundaries between the layers. Layers accommodate decoupling, and decoupling accommodates resilience to change and complexity.

Follow the layered architecture rules:

1. Lower layers do not know about higher layers

Our view layer does not need to know about the controller layer, or, really, the application in general. It simply translates DOM activity into user intention. Precisely how user intention is interpreted is beyond the view layer’s purview.

Similarly, the data has no notion of its use, and certainly has no idea about its representation in the DOM as HTML.

2. Lower layers are robust, complete abstractions

Our controller must know about the lower view layer, but it does not need to know about the DOM details. The view layer should be a robust abstraction over the DOM, demanding no knowledge of the DOM from higher layers.

Layered architecture Pros

Simplicity and Reuse

One of the biggest wins of a layered architecture is the simplicity of the layers themselves. The SearchView once handled the complexities of the DOM, as well as acquiring data, emitting analytics and updating the URL. By restricting the view layer to a limited scope of responsibility, we cured the SearchView of domain knowledge.

Removing domain logic from the view layer has the happy side effect of greatly increasing the generality of views. Decoupling discovery of the user’s intention to search from how we act upon that intention allowed us to collapse SearchView.

At this point, there isn’t anything particularly special about SearchView, giving us the flexibility to create a rather generic SearchInput class, which could be used elsewhere.

Testability Pro 1: View tests

When domain logic resides in the view, one must exercise the DOM in order to test domain logic. This often puts developers in the unfortunate position of crafting a DOM structure which is a reasonable facsimile of the production DOM structure, and then asserting that data changes when UI is manipulated.

For instance, in order to test the original SearchView, we would have to create the view’s element within the test DOM, manipulate the search input, stub out the AJAX request for search results, and assert that the results appeared correctly. The undertaking grows increasingly onerous as we introduce additional domain logic like the minimum number of characters to qualify for autosearching.

By isolating the view layer from domain logic, the testing challenge becomes trivial: Manipulate the DOM and assert that the correct semantic event was triggered by the view. That is, assert that the user’s intention was properly expressed.

Testability Pro 2: Controller tests

Testing domain logic in the controller is also simplified. As controllers expect their collaborators to be injected, we can inject stubbed collaborators. For example, a view collaborator must only support event triggering, and does not need to be a real Backbone view.

Forward Thinking

Applications grow and evolve. New requirements will emerge. Significant mechanical changes may be in order.

Layers grant us flexibility. We can completely supplant our UI without altering the core logic of the application. We can change inputs to sliders or change tables to graphs with ease. We can replace localStorage with server persistence without changing an ounce of view code.

Replacing layers Maintaining a lightweight and isolated view layer allows one to swap in alternative rendering and DOM management solutions. Convenient data binding via knockout, the efficiency gains from React or the [incredible benefits] from [some in vogue rendering library] are attractive departures from standard Backbone views. When the entire view layer is devoid of business logic, porting becomes a much more tractable venture.

Layered architecture Cons

Ceremony and Indirection

Separating simple applications into layers and distributing responsibilities demands diligence. Introducing new abstractions makes code more, well… abstract. The resulting layered architecture is initially harder to understand than longish procedural code packed into a Backbone view.

Below a certain threshold of complexity, it may be advisable to just stick with models and views.

Homegrown Headaches

Building on top of the backbone.js foundation empowers developers to make important design decisions. Unfortunately, as I can relate from personal experience, some design instincts are misguided, and too much freedom can be a detriment. If you’re worried about making good design decisions and establishing a firm architectural base, perhaps a framework with stronger conventions is a better choice.

You do you

Hopefully, the generalized structure presented in the post gives you a reasonable kernel upon which to graft your personal preferences. There are many ways to deviate from these patterns, and no objectively “correct” architecture.

Maybe you’d prefer to have controllers instantiate views rather than the App. Maybe the data layer should be isolated behind services. Or maybe the App and module abstractions should be merged.

I would encourage you to experiment, and find the conventions that work best for you.

Take a breather

That’s a wrap on Part I. In Part II, we will examine a handful of disjoint extensions to the basic organizational patterns posited in Part I. But before we get started, take a break, and prepare your mind and body by perusing this album of posing DJs.

Tweet at Foraker

Share this post!