Using Backbone.Relational as a relational model framework in Backbone.js Applications

Engineering

Learn from our challenges and triumphs as our talented engineering team offers insights for discussion and sharing.

Using Backbone.Relational as a relational model framework in Backbone.js Applications

Engineering

In his blogpost a couple of months ago, Harry talked about how we at LiveRamp use Backbone.js as our front-end framework. He also described a few plugins which make our life considerably easier, one of which was Backbone.Relational. In this blog post, I’ll take a deeper dive into this particular plugin and describe our experience using it.

Why use it at all?

At LiveRamp, we build highly configurable systems to serve the various use cases of our customers. We store these configurations in a MySQL database, and leverage its relational structure in our server side code. Since we heavily rely on the relational structure of our data model on the backend, we realized that it would be useful to employ a similar structure with our front-end data model. However, traditional Backbone models are JSON structures (with some added events and server-side sync capabilities) that do not lend themselves very easily to a relational structure. How do we solve this problem? – Enter Backbone.Relational. This relatively light-weight plugin helps tie together related Backbone  models by their ids – all you have to do is define the schema in the model definition keyed on relations. As seen in the example below, the main components of this schema definition are the foreign key (‘folder’), the related model (‘Folder’) and the reverse relation.

var Segment = Backbone.RelationalModel.extend({
  relations: [{
      type: Backbone.HasOne,
      key: 'folder',
      relatedModel: Folder,
      reverseRelation: {
        key: 'segments',
        type: Backbone.HasMany
      }
    }
  ], …

});

So, if we have a instance of the Segment model segment, we would retrieve the related Folder model through segment.get(‘folder’).

While the relationship from a segment to a folder is a has-one relation Backbone.Relational also supports has-many relations – usually done via a relation defined on a Backbone Collection. The reverse relation defined in the above schema, is a has-many relation, so given an instance of a Folder model, folder, folder.get(‘segments’) would return a Backbone Collection of the segments related to the folder.

Under the hood Backbone.Relational creates a global store of all Backbone-objects indexed by model-type and id. Since this global store is just a hash, indexing is pretty fast.

How does Backbone.Relational manage the relations?

In addition to defining the relational schema, there needs to be some coordination on the part of the backend if you are using a RESTful client-server interactions (as opposed to bootstrapping all your data from just one controller/ API calls) .

Going back to our previous example, suppose Segment and Folder are independent resources that are related but where the data is going to be fetched in different server API calls. One of the two model JSONs will need to have the foreign key with the related id in a JSON as the value. So the Segment model’s controller would return

{
  :attribute_1 => x,
  :folder => {:id => 1}
}

Alternatively, the Folder model’s controller could return

{
  :attribute_1 => y,
  :segments => [{:id => 10}, {:id => 20}]
}

Backbone relational then ties these together using its underlying store of Backbone objects resolving the foreign_key attribute to be the actual instance of the related model that shares the same id (for example, the Folder model with id = 1) . Note that if the related model already exists, the attributes present in the foreign-key JSON will be smart merged into that instance; if the related model does not exist, Backbone.Relational will usually create the related model (though this behavior is configurable).

Performance concerns

While Backbone.Relational is extremely useful, there are important performance concerns. With a large number of Backbone models, relational can slow down the client tremendously, and in extreme cases even cause the browser to crash. We have run into these issues when we had a many-to-many relationship on the front end (that brought the total objects that relational had to manage to a few 100,000). We are investigating a couple of different methods to get around this issue. First, you could manually manage the objects and their relations (or fetch them on demand) this essentially cuts down on the size of the relational store. The other option that we are looking into is to use server-side pagination to put an upper limit on how large the front-end data stores could grow to. We believe that the latter approach would be a good long term solution, which allows us to retain the tremendous simplicity that Relational provides.

Concluding thoughts

At LiveRamp, Backbone.Relational is an essential part of our front-end architecture, and we would prefer to continue to use it. The abstractions it provides significantly simplify our front-end logic while keeping our codebase elegant and maintainable. While its impact on performance is a concern, our preferred solution is to identify other avenues to improve performance rather than to do away with it altogether.