Seeking Engineering Review: How to deploy an iOS App?

Matt Dailey
Selbi
Published in
6 min readOct 18, 2016

How should Selbi do continuous delivery for our React Native app?

Background

Jordan and I have been working on Selbi for a couple months and just pushed a beta release (🎉🎉…🔥😮…🎉).

Prior to Selbi I was at Palantir and one of the things I miss most is the large number of smart and helpful people within shouting distance. This article is my attempt to crowdsource an engineering review for Selbi’s release process.

Let’s see how it goes!

Overview

This review is broken into several sections.

  • The problem statement including constraints created by our architecture.
  • The proposed solution.
  • A brief description of other considered solutions.
  • Open questions.

Any feedback is greatly appreciated but I’d particularly like to hear about any mobile technology or best practices that are missing from the discussion.

Problem Statement

We need a system to define how code goes from a development laptop to staging and production.

Prioritized Goals

  • Deployment should be fast and repeatable locally. The system should facility mundane tasks but never be a hinderance when speed is desired.
  • System should describe the git flow to manage multiple versions.

De-prioritized Goals

  • Full automation. Running a script or 2 locally is fine. This may be an eventual goal but isn’t an immediate priority.

Existing Architecture

The Selbi app is written in React Native. The backend is Firebase with a service worker. Clients talk directly to Firebase and any more complex tasks (eg Stripe or Twilio) are enqueued to be handled by backend service. This is pattern 2 in this article.

High level view of Selbi architecture.

This means we deploy code to 3 places:

  1. User’s phones.
  2. Our Firebase schema.
  3. An app engine server running in Google’s cloud.

This is actually super important because it means we can’t deprecate an API version. It rules out the possibility of doing a selbi.io/v1 and a selbi.io/v2. Our API is the Firebase Schema.

Existing Stages

Selbi deploys to 3 stages (develop, staging and production), each of which correspond to a Firebase instance.

Existing Project Set up

All Selbi code is in one repo on Github. The service worker, the backend schema and app itself each have their own directory which is a NodeJS project.

Proposed Solution

Relevant technologies:

  • CodePush — Push Javascript your mobile app.
  • Fastlane — Automation tools for releasing Android and iOS apps.
  • CircleCI — Continuous integration platform.

Overview

In this solution, we force users to update when they are on an old version of the app and only update version number if there is a native change.

This seems advantageous because most changes will be restricted to javascript so we are unlike to force too many updates. It also means we don’t have to deal with the headache of testing various JS versions against various native versions. We throw out the idea of backporting all together.

Pseudo code

Code up feature locally and push to Github.When feature is complete, merge feature into develop.Upon ready to release:
if native changes:
create new feature branch to bump version
merge version bump to develop
merge develop into staging
if JS only:
include #jsonly in merge commit message
run regression pass
Upon bugs are found in staging:
patch on develop and merge to staging
if entire release is still JS only:
include #jsonly in merge commit message
Upon staging is stable:
create branch for old version from production
merge staging into production
if JS only:
include #jsonly in merge commit message
Upon any branch updates:
run all unit tests and block other action if failing
Upon merging to 'production':
if JS only change:
firebase deploy
gcloud deploy
CodePush deploy
else:
build Production archive and send to ItunesConnect
submit for approval with Apple for manual release
await approval from Apple:
release version (manually in app store)
// All of this would be done by running a script locally.
CodePush to old versions requiring native update
firebase deploy
gcloud deploy
Upon merging to 'staging':
if JS only change:
firebase deploy (to Selbi-Staging)
gcloud deploy (to Selbi-Staging)
CodePush deploy (to Selbi-Staging)
else:
build Staging archive and send to ItunesConnect
set new Staging build for Internal Test flight
firebase deploy (to Selbi-Staging)
gcloud deploy (to Selbi-Staging)

Each pseudo-code block above can be scripted fairly easily except waiting for approval from Apple. We could likely automate that as well by waiting for an email alert from Apple but that seems overly complex.

Implementation Scripts

This section provides the usage docs for a series of scripts that would enable this workflow. Below is the project structure.

$ tree -L 1 selbi-v2
selbi-v2
├── README.md
├── Selbi // react native app
├── circle.yml
├── selbi-backend // firebase schema + validation unit tests
├── selbi-web
└── selbi-stripe-worker // backend worker
$ selbi-v2/Selbi/deploy -h
Deploys the Selbi app. It can be used to either deploy native or javascript only changes.
Usage: ./deploy <stage> <type>
- stage: One of 'production' or 'staging'
- type: One of 'native' or 'js'
./deploy production native - Build and publish the 'Selbi-Production' scheme to ItunesConnect. Submits the app for review.
./deploy production js - CodePush deploy to apps in the production environment at the current version.
./deploy staging native - Build and publish the 'Selbi-Staging' scheme at the current version and build number to ItunesConnect Internal Test Flight.
./deploy staging js - CodePush deploy to app in the staging environment at the current version.
Note that this will modify your local file structure to put all necessary config files in place for which ever stage you choose.
$ selbi-v2/selbi-backend/deploy -h
Deploys the Selbi Firebase backend schema to a specific stage or to a user owned Firebase instance.
Usage: ./deploy <stage>
- stage: One of 'production', 'staging', 'develop' or the name of a Firebase environment of the owner.

Note that this script allows deploying the schema to a Firebase environment owned by the user. This is a convenience to let each developer have their own entire Firebase instance for development.

$ selbi-v2/selbi-stripe-worker/deploy -h
Deploys the Selbi backend service worker. If you want to run the service worker for development, use 'npm start'.
Usage: ./deploy <stage>
- stage: One of 'production', 'staging'
$ selbi-v2/release -h
Releases and manages Selbi branches for the release flow.
Usage:
./release cut test js - Create a pull request from develop to staging with #jsonly in the request title.
./release cut test native <version> - Push a commit setting the version to develop and open a pull request from develop to staging.
./release cut prod js - Create a pull request from staging to production with #jsonly in the request title.
./release cut prod native - Create branch from production for current version and push to Github. Create a pull request from staging to production.
./release to staging js - Deploy javascript immediately to Selbi-Staging for entire system.
./release to staging native - Build and deploy Selbi-Staging to ItunesConnect Internal Test Flight as well as deploying the entire backend.
./release to production js - Deploy Selbi, selbi-backend and selbi-stripe-worker javascript immediately to Selbi-Production.
./release to production native - Deploy javascript forcing native update to versions besides the current one and deploy backend services. You must manually release the newest version from ItunesConnect

These scripts suite the purpose of providing an easy way to manage the deployment from commandline as well as the basic tools for automation from CircleCI.

Other Considered Solutions

Another Tech Stack

The main other contender I looked at was Bitrise. Bitrise provides a CI framework and a commandline build interface. That’s pretty nice except I’m not sure I want to tie those two things together. Also Fastlane seems to have a massive following and great docs.

Not Forcing Updates

Forcing updates is a little scary because it could mean people uninstall the app. However, the massive increase in complexity of supporting various version and backporting JS only updates is too high. The trade off means we can focus on new versions being stable rather than spreading resources thinly to validate older versions.

Open Questions

These aren’t necessarily questions that need to be answered but they are interesting and I’d love feedback. 😍

  • Are there other best practices we’re missing?
  • How will this impact us once we add Android to the mix?
  • How do we properly (and efficiently) test versioning + CodePush?
  • How will we manage restricting access later on once not everyone with repo access should have deployment access?

Thanks for reading! If you want to hear what’s going on with Selbi, check us out on Medium, Instagram or Facebook.

--

--