At Sonian we have a small team building interactive visualizations where companies can better understand their massive amounts of email data. Building this kind of program is non-trivial: data needs to be synchronized between servers and complex interfaces through user interactions or otherwise while at the same time having the user perceive it as “feeling right”. Like other software disciplines it’s a combination of engineering and art.
As engineers we want to write our programs so that they have as few bugs as possible and are easily extendable, and as a company delivering value to customers we want stable features delivered quickly. Reconciling these objectives is a universal problem which is made easier in software engineering through the use of patterns and tools.
One pattern used to help with building contemporary web applications isMVC, conceived in the 1970s as a pattern for separating concerns in software programs, still popular today. Backbone is a well-known implementation of MVC where you build programs with object-oriented constructs:modelsandcollectionsencapsulate data andviewsrespond to user interactions and mutate the browser DOM. Objects communicate with each other through method calls andasynchronous events. We started this project using Backbone simply because that’s what we were more comfortable with, having using it extensively (and arguably successfully) since 2011.
The complexity of the software systems we are asked to develop is increasing, yet there are basic limits on our ability to cope with this complexity.
As we progressed through requirements, we had built collapsible panes, modals, popups with dynamic and navigatable content, full-text search filters, data sorting and pagination, and so on. Every one of these parts had to work in concert so that the UI represented the right context for the sequence of actions a user applied in any of multiple available interconnected interactive D3.js visualizations.
The code behind the features turned complex fast and we reached a point where a fair portion of effort was spent on maintenance. This situation is certainly familiar to most.
Contemporary event-based MVC architectures can be viewed as agraph of stateful nodes communicating asynchronously.
In Backbone, particularly, despite following best practices and using complementary tools likeMarionette, it’s likely that as the codebase evolves it ends up with:
This kind of construction makes it hard to reason about the program. In addition to globalmutable statethere are events being dispatched likely causing multiple objects to change their state, which in their turn may dispatch even more events, possibly in ways you didn’t anticipate, making the program harder to extend and debug.
Programs that use state in a haphazard way are very difficult to understand. For example, if the state is visible throughout the whole program, then it can be assigned anywhere. The only way to reason is to consider the whole program at once.
While it’s still entirely possible to build complex and useful programs using this kind of architecture (people have been doing it for years), maybe there’s a better way to write software where we don’t spend so much effort juggling with complexity unrelated to the actual problem at hand.
An alternative tool for helping build large-scale web applications is React. Open-sourced by Facebook, React is a “library for creating user interfaces”, and it works by having the programmer express how their UI should look likeat any point in timeand having React automatically manage UI updates when the underlying data changes, obviating the need for explicit event observation or manual DOM mutation.
(…) programmers did not know how harmful complexity is, and secondly they did not know either, how much complexity can usually be avoided if you give your mind to it.
The basic unit of construction is thecomponent, which is essentially a function: composable by definition. Components are lightweight, in-memory representation of the DOM, and as their input changes React diffs these representations and batch applies the minimal set of changes required on the real DOM. The best part is that when using React you don’t have to think about any of this: just build components! React does the hard work under the hood.
This is in our opinion a much less leaky abstraction for synchronizing program state with its visual representation than any of the current solutions. It allows you to build programs using functions and data structures rather than framework-specific constructs or markup, and it makes entangling logic from your problem domain with observables a thing from the past.
React draws a lot of inspiration from functional programming. It promotes immutability through the clear separation of
stateand encourages writing functions (components) that are free from side-effects and always return the same values (DOM representation) given the same inputs (props).
The functional model of computation combined with immutable data and one-way dataflow makes it much easier to reason about your program, greatly facilitating extension and debugging.
We got to know React late 2013 through the Clojure community, which quickly adopted it as a solution for theDOM problem. Our transition to it started by replacing a complicated composite, nested Backbone view that showed related word clouds side by side and allowed the user to perform interactions with either individual or groups of words. The existing implementation involved a lot of state and several events. It was hard to understand and had noticeable lag when the word clouds were too big. It had been a few weeks since we wanted to start using React, so we took this opportunity to see if it could better handle complexity. After literally one day of learning we started the transition, and after two days we had replaced the view with a React component with less code and more features. Its performance with even bigger word clouds was smooth even though we didn’t write a single line thinking about performance and it integrated nicely with every other part of the Backbone application.
If you knowomthis certainly looks familiar.
The application state is an immutable hash map provided bymori, which we use to represent every piece of data. Transactions are pure functions that return transformations of the current application state, which the state manager uses to transition to new states. With state and its possible transitions isolated to a single place, the rest of the application is essentially just logic for displaying data. This approach gives us amodel of timefor free, since the state manager has access to the application state history. With time, application-wide undo and redo is trivial.
Sonian already uses ClojureScript and om in other internal projects and we’ll be transitioning to that soon.
React is a powerful tool that we found to be objectively better than contemporary MVC frameworks. It provides a simpler abstraction with the collateral benefit of delivering faster performance. It’s helping us build UIs that would be nearly impossible to build using current alternatives under our current constraints.