Harry Potter and the Magical Framework

Ownership, simplicity and why React may have found the sweet spot

I recently gave a presentation on the state of front-end development, covering various topics like growing pains we experience at CrowdTwist and the role frameworks play as we try to scale the product and team. I also briefly touched upon React and the potential it has to solve many of our problems. If you’re considering using React for your next project, or simply have some experience developing large front-end applications, I encourage you to check out the slides.

One of the main areas of focus was on the costs and consequences of using magical frameworks, like Angular. I would like to briefly talk about some of the experience I had building various single page apps at CrowdTwist, lessons learned, and why I feel optimistic about the future.


Mr. and Mrs. Dursley of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much.
Harry Potter and the Sorcerer’s Stone

It was late 2012 when we began to redesign FanCenter, the user-facing portion of our loyalty platform. The idea was to build a responsive web app where the client-side code was actually developed and maintained on the client-side. Prior to that, the PHP back-end would spit out html inlined with data to the browser.

We chose Marionette to build out the app. Keep in mind it’s 2012, it will be at least a year until Angular starts gaining steam. Besides, the simplicity of Backbone attracted us, providing just enough tools out-of-the-box to reason about the architecture in an MVC way.

Marionette sits on top of Backbone and provides boilerplate views, like CollectionView and Layout, so that you can write less code to do the same things over and over again.

Screenshot of FanCenter home page for EAS.

We were able to leverage the various boilerplate classes to establish conventions in our app and compose the many dynamic pages and subviews, as you see in the home page above.

At the end, we had written around 20,000 source lines of code (not including the jade templates, which had some logic of their own). That may seem like a lot, but not unreasonably so. We knew Backbone was a batteries-not-included library, and even with Marionette we had to write a lot of our own code around nested routing, view lifecycle control, form bindings and validation, etc…

Backbone / Marionette had its shortcomings:

  • Many moving parts (different utility classes trying to synchronize state and manage templates)
  • Poor view lifecycle control
  • Spaghetti events around DOM
  • Simple routing (difficult to manage states, nested views, and appropriate re-renders)

Essentially, we ran into many hurdles involving the details and intricacies of starting with very little and trying to do much more. While constrained minimally with Backbone and Marionette, there was a lot of room for us to make mistakes. Naturally, we made those mistakes, and learned from them (or so we thought).


Original oil on canvas by Jean Auguste Dominique Ingres

2013–2014 was the Golden Age of Angular for CrowdTwist, and I presume for many other companies as well. This makes sense, for Angular presented a very attractive, batteries-included solution for rapidly building complex functionality. As someone who has just emerged from the depths of the muggle world, reaching out for the Angular wand was very tempting.

And so we used Angular to build the new client-facing Control Center, a basic CRUD application with lots of dynamic forms and tables, as well as Widgets, an application that enables customizing and embedding various components from FanCenter into third-party websites, like a user’s account overview.

The blue account overview area on the right is one of our widgets sitting inside an iframe.

Thanks to Angular’s expressive DOM and the magic of directives, we were able to move at an incredible pace, implementing features left and right while letting the framework do the heavy lifting for us. There was a catch, though, and it took us a while to fully realize what we have been giving up and the cost of doing so.

Frameworks make certain assumptions about how an application will function, providing tools out of the box to greatly simplify the development process as a whole. They tend to stress thinking less and doing more, faster. For certain use cases, like basic CRUD interfaces and smaller single-page apps, they are a good solution. The problems become more apparent when dealing with larger applications with a lot of moving parts and a team of developers in flux.

Consider a form view leveraging a custom directive to render a dynamic list of inputs (we have a similar one in our Control Center):

Now ask yourself, who owns the view(s)?

View ownership is important. It may not seem so at first when you’re building out functionality and the code just works, but you begin to feel the pain points with multiple developers trying to contribute code and new members joining the team.

Development is feature-oriented; at the end of the day, you are building features and product functionality, not directives. Sure, directives are some of the tools you can leverage to help build features in a scalable and maintainable fashion, but the end-goal is the features you ship, the products that users interact with.

Ownership of the view is the key to simplicity in front-end development. If I understand which components of a system encapsulate the logic for a particular feature, I can have a more productive session refactoring and adding new functionality.

In the example above, there are 4 different controllers. Half are explicitly managed by the developer, and the other half are implicitly created by Angular to facilitate its magical control over state and the DOM. Ownership, much like state, is difficult to manage and predict in a framework like Angular. Its two-way bindings make it an attractive option in the first place, but with extensive experience and building custom directives and components, the dark magic of the digest loop and watch calls makes it difficult to manage the increasing amount of logic going on under the hood.

You may argue that these magical tools can be easily misused, and it is up to the developer leveraging them to make smart decisions and use them properly.

You may say “just because you’re using Angular doesn’t mean your code has to suck”.

That is true. However, these tools that Angular provides make it harder to grow an idiomatic codebase. The framework has its special sauce and set of (increasingly changing) rules you need to understand and follow if you want to write efficient and maintainable code.

This makes AngularJS a simplicity trap. It is very attractive when coming from Backbone where you have to do almost everything yourself (not an ideal situation either). The problem is you relinquish a non-trivial amount of ownership and control over your application in hopes of the magic paying off. When dealing with large applications that consistently grow in features with an increasing amount of developers, ownership and true simplicity become more important.

This is why I’m particularly excited about React: it may have just found the sweet spot.

Using React, you build Components that co-locate the DOM with your logic, optionally leveraging tools like JSX to blur the lines between template and logic.

Here is a basic React component using ES6 syntax as well as JSX for the html:

import React from "react";
import Question from "views/question";
let QuestionList = React.createClass({
render() {
let questions = this.props.questions.map(question => {
return <Question data={question} />;
});
    return (
<div class="questions">
{questions}
</div>
);
}
});

If you think that’s breaking all the rules, newsflash. You’re doing it already:

unwatch = $scope.$watch('survey', function(survey) {
if (survey) {
unwatch();
$scope.survey.hasQuestions = survey.questions.length > 0 ? true: false;
}
});
<form name="surveyForm">
<input type="text" ng-init="survey.title = survey.title || 'Default'" ng-model="survey.title">
<input type="checkbox" ng-model="survey.hasQuestions">
</form>

The Angular example is slightly contrived, and there are multiple ways (all pretty similar) to go about implementing the desired functionality, but the idea remains the same. The coupling between the template and the view controller is there. Separating the two is often quite difficult and you will need to jump through a lot of hoops.

This is where false separation of concerns comes into play. For both Marionette and Angular applications, the controllers and templates are kept separate, but more often than not the explicit and implicit dependencies between them exist.

With React, you at least get all the dependencies isolated to a single component in a single file. That is ownership.

This model lends itself quite well to the notion we explored earlier around building features. With React, you can easily structure your application so that your features can directly correspond to your components.

Understanding the state of your component is more straightforward in React as well and fits quite well into the idea of ownership.

A component’s data is managed by 2 objects, props and state.

props is the configuration of your component passed in as attributes. It is immutable. You can also consider that the component that manages the props passed to another component owns that component.

state is private to that component and defines the mutations a component undergoes, typically resulting in a render.

let QuestionList = React.createClass({
getInitialState: {
hasAnswer: false
},
  processAnswer(answer) {
this.setState({
hasAnswer: true
});
}
});
<QuestionList title="Answer these" />

title is a prop that is passed into the component. hasAnswer comprises the state.

With React, you know the exact state a component is in at any given point in time, and the UI will reflect that consistently.

With Angular, it is a lot harder to tell what the state of a particular scope is, depending on ongoing digest loops, watch calls, and other magical constructs.


To conclude:

  • The problem of scaling large applications with magical frameworks is real
  • If you think you separated your concerns, think again
  • Ownership is important, how would you define simplicity?
  • React’s approach is to keep it small and contained.

If you enjoyed this post, you may want to check out the presentation as well.