Jiří Zajpt home

The architecture of Ember.js apps - data

In the previous article about architecture of Ember.js apps I've covered the MVC and statechart interaction. In this post I'd like to take a look at what I think is most clean way of handling data and the interaction with a server in your apps.

Please take a not that the architecture described in this post comes from my experience of using SproutCore 1.x and its datastore framework. I've just taken liberty describing it and the my reasoning behind its use.

AJAX and the server

Let's start by stating obvious fact: most applications you write are somehow required to handle data. And when writing JavaScript web application you'll probably be using AJAX as a primary means of retrieving and manipulating data on the server. Let's take a look at imaginary book store app.

Our task is to get the list of books to display. Thanks to jQuery we have at our disposal a common set of API for dealing with AJAX requests and because Ember.js itself requires jQuery for DOM-manipulation, we don't have to introduce any new dependencies to our apps.

So let's write the code. We want to have booksController with populate method that is supposed to do the labor of fetching the data from server and assigning it to the content property:

App.booksController = Ember.Object.create({
  content: null,
  populate: function() {
    var cc = this;
    jQuery.get('/books.json', function(data) {
      cc.set('content', books);
    });
  }
});

This seems that it could work. The populate method fires AJAX call to server and when server responds, our callback function which sets the content property gets called with returned data.

Now let's try to think about how we would test our booksController. The populate method has a dependency on 3rd party library (jQuery) and one rule I like to follow is that you should never mock the types you don't own. In our example the type we don't own is the jQuery library - we cannot change it's interface, we cannot refactor it, we just have to use it as it is.

So we've got some testing issues, but let's suppose we are the the hero-programmers who don't need to test and our code and its design are always flawless. If we'd continue writing more controllers that also fetch something from the server we'd end up with a lot of related code split across many controllers in their populate and possibly other methods. And even hero-programmers have to admit that's just not OK.

As tests tried to told us just before, this just calls for refactoring. We refactor the code that talks to the server onto the new object which is called data source. A data source is in fact an adapter to jQuery interface (and by extension an adapter to our server API).

So the first and naive implementation of data source that is supposed to fetch books from the server may look like this:

App.DataSource = Ember.Object.extend({
  getBooks: function(callback) {
    jQuery.get('/books.json', function(data) {
      callback(data);
    });
  }
});
App.dataSource = App.DataSource.create();

Now we have to change the controller implementation to use App.dataSource.getBooks method. This also means that we can now safely mock the dataSource in our test (it's the type we own now) of the given controller and we also have all code related to server interaction in one object. Clearly, winning.

App.booksController = Em.Object.create({
  content: null,
  populate: function() {
    var controller = this;
    App.dataSource.fetchBooks(function(data) {
      controller.set('content', data);
    });
  }
});

So by introducing the concept of data source to your application not only you'll end up with cleaner separation of concerns, but your controllers will also be easier to test.

The data in your app

Now that we have encapsulated communication with server in a data source, let's think about storing of objects retrieved from server in our app. In the first example shown after the data was returned by the server, we've just set a controller's content property with array of returned objects. This may be enough for simple apps with 1 or 2 controllers, but it's not sustainable approach for any bigger application.

The proper solution is to introduce another object into our application - store. The store's responsibility will be to encapsulate all domain objects in our app and provide meaningful access to them.

Before we move to the details, first we should go back to our data source. Since we want to introduce a store we should modify data source so that it loads the data from the server inside the store:

App.DataSource = Ember.Object.extend({
  store: null,
  getBooks: function(callback) {
    var store = this.store;
    jQuery.get('/books.json', function(data) {
      store.pushObjects(data);
      callback(store);
    });
  }
});
App.dataSource = App.DataSource.create({
  store: App.store
});

A couple of notes on this code. First, we're pushing objects from the server to our store and if App.dataSource.getBooks() gets called twice, we'd end up with a store that contains all the records twice. A solution is of course to erase the store before pushing objects to it. Second, a callback gets called with a store as a parameter so that in our callback we can work with store associated with this particular data source.

A simplest store that matches needs of our data source could look like this:

App.Store = Ember.ArrayProxy.extend({
  content: null,
  init: function() {
    this._super();
    this.set('content', []);
  },
  find: function(id) {
    var content = this.get('content');
    return content.findProperty('id', id);
  }
});
App.store = App.Store.create();

The code for the store is of course a bit naive and simple, in fact the store itself is just fancy array (array proxy to be exact), but it should illustrate the point of creating a store and it's basic responsibilities - being able to push new records to store from data source, to get record by its ID and so on. As your app grows, you may want a store that is capable of handling multiple types of objects, querying support… Implementing these features is not in the scope of this post, but the code shown above should be enough to illustrate a point of store in Ember.js app architecture.

Data persistence library

If you like the principles shown in this article, I recommend to look at persistence library for Ember.js called data, which follows architecture described in this post. I have to confess that I've yet to try it, because we're using our own, simple and dumber implementation for now.

So if you have a simple app you can try to code store and data source(s) by yourself, but if you're trying to code something bigger I suggest you take a look at data library first.

A working example

Topics covered in this and previous post may seem enough for writing example Ember.js app with statecharts, data source and store. But there's an important piece missing and that is testing. As I mentioned in previous post unit testing Ember.js objects and acceptance testing whole app is important piece of architecture. So my last post about the architecture of Ember.js apps will be about testing and by the end of that post you'll be presented with working Ember.js example app - a bookstore.

Fork me on GitHub