Experimenting with the Architecture of Ember.js

What do you get when you cross Ember.js with the Single Responsibility Principle? I recently decided to find out by refactoring the architecture of Ember.js so that each class had only one responsibility. This is the result of my experiment.

Three Layers

You can think of a software application as having three layers, each having a single concern.

Three layers of software: display, state and persistence.The top layer represents the user interface, and its job is human interaction. It provides a means for people to provide input to the software and it provides human-friendly output.

The middle layer is the brains of the software. It contains the entire state of the system and the relevant logic.

The bottom layer is the software’s long-term memory. It preserves any data you would want to be remembered after the user has finished their session with the software.

My architecture experiment involved an app that had an Ember.js front-end and a Rails back-end. In this context, the display layer is the web page, the state layer contains Ember objects, and the persistence layer is Ember Data talking to the Rails back-end.

The Display Layer

I determined that there were four main concerns in the display layer.

The first concern is producing HTML for the browser to render to the user. Ember.js uses Handlebars Templates for this.

The second concern is keeping the HTML DRY. Ember.js provides Helpers for this.

The third concern is that the user needs some way to interact with the software–to provide data or trigger an action. Ember.js gives us Controls to do this, such as Ember.TextField and Ember.Select.

The fourth concern is DOM-related behavior. This is behavior not related to the state of the software, but to the user interface. One example would be auto-focusing on a form element. Another would be adding a jQuery animation. Ember.js does not give us a class with this single responsibility, so I introduced the concept of a Pane.

The State Layer

Ember Data provides us with a nice DS.Model class for representing objects that are meant to be persisted. However, some application state does not need to be persisted, such as which tab is selected or a currently selected item in a list. To distinguish between these two types of objects, objects that are meant to be persisted I call Storables and objects that are used to remember the display state I call Viewables.

In Between the Layers

Ember Data takes care of the communication between the state layer and the persistence layer. But what about the communication between the state layer and the display layer?

When the state of the application changes, the display needs to be updated. Ember.js provides a wonderful mechanism for this, called Bindings. So Bindings are the mechanism for the state layer to affect the display layer.

For the display layer to affect the state layer, I created a new type of object called a Handler. Controls are always hooked up to a Handler. A Handler receives the action generated by the Control and, in response, changes the state of a Viewable or Storable. That’s all a Handler does: receives an action from the display, responds by changing state. Ideally, it has no knowledge of the DOM.

Tying It All Together

So what changes did I end up making to the architecture of Ember.js?

For one, I was disciplined about separating state from display. Ember.View contains code that relates to the display (DOM-related JavaScript) as well as state (properties that are fed to Templates). I separated these concerns into two entities: a Pane which holds the DOM-related JavaScript and a Viewable which holds the properties (state). Note that there tends to be a one-to-one relationship between a Pane and a Template, as well as a one-to-one relationship between a Pane and a Viewable.

I also separated out the concerns of Ember.js Controllers. Ember.js doesn’t have a Controller class per se, but there is an idiom where Controllers have three concerns:

  • holding a collection of Ember objects
  • responding to actions coming from Controls by changing state
  • creating calculated properties related to the collection

The todosController is a good example of a Controller having these three concerns.

I split up these concerns like so:

  • holding a collection of Ember objects — I created a separate class for this. It felt very natural. The class contained the collection of Storables as well as related methods like create.
  • responding to actions coming from Controls by changing state — I called this a Handler.
  • creating calculated properties related to the collection — these properties, being state, would end up in either a Viewable or Storable class.

The important thing to notice is that each component of this architecture is responsible for only one thing, so that when you develop a new feature, it’s easy to decide what code goes where:

  • Templates: hold the HTML
  • Helpers: keep HTML DRY
  • Panes: contain DOM-specific code
  • Controls: provide a place for user input
  • Handlers: initiate state changes based on user input
  • Viewables: represent temporary application state
  • Storables: represent state that should be persisted
  • Bindings: communicate changes in state to the relevant listeners

But Will It Blend?

So what was the result of my experiment? Was it a success?

I only took the experiment so far, but as far as I got, it seemed to work. There are definitely some ideas in here worth considering. That said, I won’t say that it’s objectively better than what Ember.js provides out of the box.

It’s to Ember’s credit that I was able to tweak the architecture so easily. I didn’t have to modify any of Ember’s core classes, just play around with names and use reopen to split the Ember.View class between two files.

Splitting the Ember.View class seemed the least natural. Even though Ember.View contains both state and display concerns, there is a certain cohesiveness there. That said, it felt really clean to be able to think of the app in the clearly defined chunks of “display” and “state”. It made decisions of what goes where easier.

The Controller refactoring was one of the best wins. Controllers in Ember.js seem like the least cohesive part of the system.

All in all, it’s a matter of taste and scale. Taste, because this experimental architecture results in lots of little files. Some people don’t like to program that way; they don’t like constantly switching among many files. Scale, because you generally only see the benefits of splitting responsibilities out so finely when the number (and complexity) of features grows very large.

There’s been a fair amount of discussion recently about increasing the object-oriented nature of Rails code. I think this is in part a small correction of the original Rails architecture which wasn’t quite as carefully designed as it could have been. I think Ember.js is young enough that it has an opportunity now to revisit whether it’s as well-designed as it could be. Ember.js has a very strong object model, and it’s a near paradise of object-oriented programming in JavaScript. It would be a shame if such a clean, well-designed architecture ended up with poorly designed Controller classes, for example.

Recommendations For MVC-Inspired Architectures

In light of the recent Cambrian explosion of MVC-inspired web architectures, I have some advice for would-be architects.

#1: Be Intentional About Separation of Concerns

It’s not correct to say that you have to follow the Single Responsibility Principle all of the time. But if you don’t, it should be on purpose. Too many MVC implementations end up with a Controller class that is a soupy mess.

#2: MVC Does Not Mean Three Classes

An MVC-inspired architecture does not mean you have a Model class, a View class, and a Controller class. The architecture presented in this post is strongly MVC, yet has many more than three classes.

#3: Use Intent-Revealing Names

One of the most important skills of software architecture is good naming. Names should clarify the intent of the class, not obfuscate. What this means is that you should consider no longer using the names “Model”, “Controller” or “View” as class names. This is because these names have been used in so many different ways by so many different MVC architectures that when you use these names, you are asking a new developer to first unlearn a previous concept they already associated with that name. You are adding friction. Names should reduce cognitive friction.

But even if you can’t stomach a complete moratorium on the names Model, View and Controller, I claim that you should never use the name Controller. Your architecture design will be better for it. First of all, Controller is not an intent-revealing name in the first place. What, exactly, is it controlling? It’s a design smell, if you ask me. Backbone.js gets this right. It doesn’t have a Controller class; it doesn’t even claim to be MVC.

I hope you found this Ember.js architecture experiment useful and thought-provoking.

You can skip to the end and leave a response. Pinging is currently not allowed.

6 Responses to “Experimenting with the Architecture of Ember.js”

  1. Anders Bälter says:

    Very nice article! Half way through I was like “why is he giving new names to old stuff?”, but I definitely see your point.

  2. Great article, Wyatt! I’ve been thinking for a long time that I need more conceptual structure for the JS stuff, and this really helps.

  3. wonk says:

    Lovely and stimulating article.

    I know when I have deep thoughts such as yours, my mind often ends up jumbled. I love the way you break the app down into pieces… using a four-layer perspective somewhat different than MVC but which dovetails beautifully with MVC. And instead of becoming jumbled, you solidify our understanding of ember.js and web app design at the same time.

    Finally, the graphic makes the article. I referred to it several times. It is the signature I shall refer back to. A picture truly is worth a thousand words, even in technical articles.

    Well done, and thank you for sharing.

  4. Panagiotis Panagi says:

    Great insights! It would be great if you included some code for the Handler class you created. I was and still having a hard time to get that part right.

    For the Viewable part of the app, I have somewhat followed a similar path: I implemented a region tree for dividing the screen into regions (with siblings, ancestors…). You provide the app with the region structure, and using a StateManager I tell the app what to display in each region (header: ViewX, main: ViewZ, etc.). I think it provides a nice interface and hides away the complexity of adding/removing views. I’m still working on the Control part and how to avoid duplicate code.

  5. techiferous says:

    Hi Panagiotis,

    Here is some sample handler code as you’ve requested: https://gist.github.com/2758986

    Imagine a list of employees. When you click an add button, a form appears where you can add a new employee. This form also has a cancel button to hide the form. And it has a save button to create a new employee. There is also a details toggle button on each employee in the list.

    As you can see in the handler code, there is a JavaScript method corresponding to each action that the user can take. The handler responds to the user action and initiates one or more state changes in response. In effect, the handler “pushes the first domino” and then a chain of changes bubbles through the state layer, powered by Ember bindings.

    Notice how the handler does as little as possible. It basically just translates a user action to a state change. It has very little logic, but there is a place for the logic of checking for any conditions that should prevet the state change from occurring. Hence the form validation conditional in the handler.

  6. Shairez says:

    Great article!
    I’ve been struggling with this MVC issues for years, you just wrote a great plan for separating client side architecture the right way IMO.

    Separating to Storables and Viewables finally being addressed as not just a “Model” but as separate concerns.

    Great job!

Leave a Reply