Lessons Learned from Creating a Reliable Mobile Build
PagerDuty engineers are obsessed with reliability. Letting down customers when they’ve been paged is the worst. With that in mind, we’re always designing and thinking of ways to maintain and build systems that maximize resiliency — including our mobile apps.
After the release of redesigned mobile applications last October, we’ve been shipping new features and updates that improve the reliability of those apps. From the perspective of a user, an app that consistently crashes or freezes is almost as bad as the services and APIs behind the app being completely unavailable.
Using software artifacts to simplify complicated builds
“Dependency Hell” is a Real Thing for Hybrid Applications
To get an idea of the complexity, here’s a diagram of PagerDuty’s mobile dependencies for 3rd-party code:
There’s also the complexity of the build tool itself. Here’s a diagram of the gulp.js packages that our default gulp task needs in order to build a PagerDuty’s app. The build process (among other things) compiles from coffeescript, concatenates files and creates front-end templates:
Which libraries should I use? (jQuery vs Zepto, Hammer.js, Backbone vs Ember vs Angular vs React)
What templating language should I use? (handlebars vs underscore templates vs mustache)
Should I use a library dependency manager like bower.js or component.js (or just npm)?
Which build tool? (gulp vs grunt vs rake vs ant vs browserify)
What testing framework? (qunit vs jasmine vs tap)
How are you going to use NPM?
Lesson 2: Leverage tools you understand and know how to use, but understand their limits
There was a tantalizing alternative, though. What about npm? It has lots of great things going for it, we understood how it worked. If we put a package.json file inside of our native application repositories, it’d only take a quick “npm install” to fetch our mobile application repository (which was a CommonJS repository) and all of the dozens of dependencies it needed to build itself. Our package.json looked just like this:
Instead of fetching a published package, NPM fetched our project from private github repository at a particular commit, allowing us to arbitrarily point to different revisions. We then had another script that ran gulp.js and built the app after it had been fetched. Magically, we eliminated all of the checked-in compiled and transformed code into github. We just needed to generate the app before bundling it inside of native applications.
NPM came so close, and perhaps with a privately-hosted npm repository we could have made it work. Using github as the source for our app package, though, the only way we could have reduced complexity in our native builds would have been to check the transformed source code. We needed to use something that had a concrete idea of build artifacts.
Lesson 3: Ask, “Do we already know the best practice?” or “Have we already learned this somewhere else?”
In September 2013, github released their Release API. It allows developers to create a release object with a tag in git repository. More critically, it also has the concept or an arbitrary number of artifacts associated with each release. In other words, we had a pseduo-artifact repository built on top of our revision control system that was already part of our daily workflow.
While we could have used a more professional artifact manager like Ivy or Nexus (or even stored artifacts in some sort of cloud storage like Box or S3), the github integration made it particularly easy.