Fresh Set of Downs
At the outset of building a new product, selecting a technology stack to use usually boils down to two questions: “What do you know best?”, and “What is everyone else using?” It’s not hard to discern why this is the case:
- The minimum viable product often manifests as the core feature set thinly wrapped with the necessary auxiliary functionalities to make it useable. If the chosen stack works well for the MVP, then it’s probably good enough for the foreseeable future.
- Since there’s usually only at most a handful of people involved with building the product, selecting as to optimize for the speediest iteration makes sense, and a healthy developer community for your stack’s components will greatly accelerate progress.
- If a certain technology works well for high profile industry entities, respected colleagues, and peers, it will probably work well for you too.
If the MVP begins to achieve product/market fit, sustaining growth will demand rapid-paced enhancement in breadth, complexity, and scale. The codebase will swell in multiple dimensions, new hires will broaden the code pipeline, and complications of scale will arise. Somewhere along the line, technical teams may come to realize they’re not entirely happy with a part of their original tech stack.
Reading the Defense
This dissatisfaction is bittersweet, as every nascent startup founder longs to one day deal with challenges stemming from scale. Yet, successful or not, an obstacle to progress is still an obstacle, and the consideration of supplanting a segment of the product stack is by no means a trifling one. Such an undertaking resembles reinventing the proverbial wheel, something that ostensibly should be avoided by a young company. Facebook’s “incumbent inertia” kept them loyal to PHP, and through a good deal of optimization and standardization it worked out for them. Conversely, Twitter famously phased out Ruby for JVM-centric technologies, which was undoubtedly a painful process, but it would appear the fruits of their labor are plentiful.
The pain point technology will differ between companies. You may have initially built upon MongoDB, and after a year you might really wish your database featured ACID transactions. Perhaps you started off with Apache Solr, and 18 months in you’re hungrily eyeing ElasticSearch’s complex document support.
Calling an Audible
The existing tools were initially built in Backbone and Marionette. This combination offers a minimalistic starting point, favoring configuration over convention. Incorporating the backbone-relational and backbone-mutator extensions, it also provided sturdy model and collection management upon which to build out v1 of our our product. Marionette takes care of much of the boilerplate messiness that usually accompanies Backbone applications, and jQuery and underscore are ubiquitous libraries that we were extremely familiar with which allowed us prototype very quickly. Require.js was used as the module loader to tie everything together.
While Backbone and Marionette were sensible choices when initially building our product out, code maintenance and upgrades for the tools were cumbersome undertakings. The flurry of activity that occurs within the internal tools required furious diligence and fistfuls of boilerplate code in order to maintain a performant application free of memory leaks. Marionette ameliorated much of the headache involved with scaling a Backbone app, but it was far from a solution.
Coming on as the first engineer, I had experience with AngularJS and an ability to offer insight into how it could solve our problems. AngularJS was mature enough at this point to be a legitimate consideration, and we began to have serious discussions surrounding a transition away from Backbone and Marionette.
Post-Snap Coverage Read
Any company that has nurtured its codebase from infancy will to some degree experience a status quo bias. We are rightfully averse to change because along with the chasm of uncertainty comes the daunting task of a monolithic rewrite. However, we at DoorDash knew our current solutions would not hold up for long, and a hefty rebuild was going to occur in one form or another anyway. If a framework shift was ever going to happen, the timing was such that friction would be minimal if we pulled the trigger right away.
But what should factor into our decision? Our discussions ranged from performance problems to implementation practicality to adoption rates to syntactical strangeness. Though we did eventually decide to switch to AngularJS (which ultimately was the right move), much of the discussion was pedantic and misdirected, largely because we weren’t asking the right questions.
What we asked: What other companies use AngularJS?
What we should have asked: Is adoption of this technology growing, and does it have a quality development team behind it and a vibrant and enthusiastic developer community?
Answer: AngularJS is growing like a weed (http://www.google.com/trends/explore#q=angularjs). With Google taking over the AngularJS project, and with brilliant minds like Miško Hevery, Igor Minár, and Vojta Jína at the helm, the project is in good hands and has a very sunny outlook. The developer community is extremely healthy: with cornerstone third-party extensions like AngularUI, Restangular, AngularStrap, and angularFire, the blistering pace of the release cycle, and AngularJS 2.0 on the horizon, the framework’s legitimacy grows ever stronger.
What we asked: Isn’t the AngularJS learning curve really steep?
What we should have asked: What does this technology provide that our existing framework doesn’t, and will it make our codebase maintainable and scalable?
Answer: AngularJS offers implicit data binding, directives (arguably the bread and butter of the framework), easier memory management, baked-in testability, better modularity (in several ways), and less boilerplate code, to name a few. Keeping our real-time internal tools performant will be more manageable, and code maintenance will be considerably easier. AngularJS’s convention over configuration methodology will allow us to keep our codebase homogenous and more accessible to new hires.
What we asked: How big is the AngularJS file size? Is it bloated?
What we should have asked: What is the total size of the framework file plus all the third party libraries and extensions we’re going to need to staple on to make this work for us?
Answer: In our stack, Backbone at the very minimum needed Marionette, underscore, and jQuery at the get go. AngularJS demands none of these, as it provides its own faculties for much of the underscore and jQuery libraries. AngularJS also makes require.js unnecessary (although there is some debate on this). AngularJS was also split into modules as of the 1.2 release: AngularJS proper, ngRoute, ngAnimate, ngResource, ngTouch, ngSanitize, and ngMock, all of which can be incorporated into the application only if needed. AngularJS can incorporate Restangular for a formal model architecture, but ngResource is often suitable for the task. AngularJS also comes out of the box equipped to deal with most cross-browser and cross-region compatibility. Its included test features are also vastly superior to anything involved with Backbone.
What we asked: What is the performance of AngularJS like?
What we should have asked: Is technology going to allow us to effectively extend it to do what we want when we grow 10x? 100x?
Answer: A fairly common topic to ding AngularJS on is performance at scale. Specifically, when built-in directives or other out-of-the-box components are used on huge numbers of templates, or elements, or lists of data, performance degrades - usually from a critical mass of bound data. AngularJS is surely not alone in terms of performance degradation at scale. The important aspect of considering a framework is deciding whether or not it will enable you to build components that can scale in the ways that you want. In the case of AngularJS, custom directives are a powerful and expansive tool that give you extremely granular control relating to performance and interaction with the outside application. Furthermore, targeted data binding and object hierarchy management allows for developers to efficiently control dirty checking overhead. AngularJS may be strict about its conventions, but this certainly does not hinder your ability to create extremely performant applications.
What we asked: Why doesn’t AngularJS have [handful of features that other framework has]?
What we should have asked: Since this technology fills most of our requirements, can we efficiently build the handful of features we need that it doesn’t have?
Answer: If you compare AngularJS to other, larger frameworks like EmberJS, there are obviously going to be some gaps. EmberJS affords formal models, object instance reuse, conveniences with getters and setters, etc. For us, the lack of formal model objects was a bit of a pain point, as all model data in vanilla AngularJS must reside in the $scope hierarchy, which can get messy at scale. However, the handy Restangular extension was seamlessly incorporated, and this has largely been an excellent solution for us. Even if Restangular or any third party library that fit our needs didn’t exist, AngularJS is flexible enough for us to have be able to architect a robust solution to this problem.
Ultimately, we’re delighted with our choice to make the switch to AngularJS. The team is still in the process of fully replacing Backbone/Marionette throughout our stack, and getting everyone familiar with the ins and outs of the framework did take some time. Replacing part of your tech stack late in the game is an arduous process, and doing so should not be taken lightly.
When deciding whether or not to adopt any technology, the ideal line of questioning should involve how the change will allow developers to ship features faster, build a better product, and write cleaner code. It is often the case that there are no completely wrong choices, but making the best decision requires that you are asking the right questions.