Tramline is a modest Rails monolith (as opposed to a majestic one) that wants to stay a monolith for as long as possible. We tend to only mildly impose advice such as “you should always start out with a monolith” or “micro-services are actually a terrible idea for a startup” for our engineering decisions. As with most practical things, all our decisions have devils inside of the details rather than adages.
Since a release coordination platform talks to a variety of fragmented yet necessary tooling, the real value comes along when many integration touch-points are combined to ship new releases on a predictable and reliable cadence.
We presently integrate with:
- GitHub for source control
- GitHub Actions and Bitrise for post-commit workflows
- Slack for notifications and sending builds
- App Store and Play Store for shipping releases
- GitLab, Sentry, and Firebase App Distribution coming soon
All of our integrations sit inside the aforementioned monolith. This is for simplicity of maintenance and deployments. That being said, we maintain a strict domain-agnostic layer for handling API calls so that if integration endpoints do need to move out — say, for accommodating higher concurrency — the contract from the layer “above” does not need to dramatically change (if at all).
However, by integrating App Store Connect, we intentionally break our own mild constraint against opening the gates to a service-based architecture.
App Store Connect has an extensive developer API to manage releases on both TestFlight and App Store. You can do most things through the API (a notable exception being able to upload builds). The canonical library in Ruby that implements a large part of the API is the excellently named Spaceship. Spaceship is, in fact, an abstraction higher than what the API allows you to do. This is because it’s designed to be Fastlane-first. All the actions and recipes that Fastlane supports are sometimes direct interfaces implemented in Spaceship.
Using Spaceship gives us two benefits:
- Use many of the battle-tested recipes directly
- Integrate the Fastlane gem with Rails
Since Spaceship is a part of Fastlane, it makes integrating Fastlane directly with a Rails app quite tricky. Fastlane is a big gem and a lot of the code just isn’t useful to the primary Tramline service. More importantly though, Fastlane’s dependencies simply do not play very well with Rails 7.
We built Applelink, a small, self-contained, rack-based service using Hanami::API, that wraps over Spaceship and exposes some nice common recipes as RESTful endpoints in an entirely stateless fashion. These are based on the needs of the framework that Tramline implements over App Store. The API pulls its weight so Tramline has to do as little as possible. Currently, it exposes 13 API endpoints.
Tramline hops all App Store requests through this service:
In Applelink, a complex recipe, such as release/prepare, will perform the following tasks all bunched up:
- Ensure that there is an App Store version that we can use for the release, or create a new one
- Update the release metadata for that release version
- Enable phased releases, if necessary
Similarly a simple fetch endpoint like release/live will give you the current status of the latest release.
Even though Spaceship has helped us come a long way, it isn't necessarily efficient at choosing the right combination of API calls underneath. For example, while preparing a release, Spaceship could end up making 3 separate API calls to look for the app, update its attributes (setting release type, version name etc.) and then select the correct build.
In some instances it’s just better to drop the interface from Spaceship altogether and make direct API requests. Applelink tries to choose the most efficient path by making use of the correct relationships.
Since Fastlane is mainly intended as a command-line tool for build pipelines, its primary focus may not be an efficient request-response cycle.
Applelink has been operating smoothly for the past few months and this model has proven to be an effective starting point. It is possible that after a few more iterations we decide to retire the use of Spaceship. Nevertheless, we would have a solidified API in place, and it would only be a matter of swapping out the implementation.
Since Applelink is a separate service that is not reliant on Tramline’s internal state, all the API endpoints are valid outside of Tramline. It can be used in a standalone way from a CI action or a Slack bot that spits out release information periodically.
As with Tramline, Applelink is open source under the Apache 2.0 License.
Cider by Aaron Sky, aims to implement a significant portion of the API in Go which has been helpful in confirming some of our assumptions.
Store Quirks is partly maintained by findings made during the implementation of Applelink.