Versioning schema releases
This example demonstrates using a GitHub repo as a central registry that coordinates the versioning and release of subschemas. Similar to the goals of managed federation (opens in a new tab), a central registry allows subschemas to be precomposed and tested together before releasing into production. This isn't prohibitively difficult to setup—a simple Git repo with some light code wrappings can generally get the job done as well or better than hosted services (opens in a new tab). Using a Git repo actually offers several distinct advantages:
- Multiple subservice schemas may be composed on a branch together and released at once, affording the opportunity for hard schema cutovers across multiple services.
- Comprehensive test suites may be written to assure the integrity of the composed gateway schema, and can easily run using continuous integration services. In fact, versioning subschemas in your gateway app's repo allows CI to run tests using both your release candidate schemas and your actual gateway server code.
- Git is a defacto-standard tool with numerous frontends available, and can be adapted to meet virtually any versioning and deployment need.
This example demonstrates:
- Using GitHub API to manage a simple schema registry.
- Hot reloading from a remote Git repo.
- Running development and production environments.
Related examples:
- See continuous integration testing for ideas on testing your versioned subschemas.
Local Setup
cd versioning-schema-releases
yarn install
1. Configure a registry repo
This example uses live integration with the GitHub API. You'll need to setup a repo:
- Create a new GitHub repo (opens in a new tab) with a README (or other default file, so long as the repo is not empty). Public or private doesn't matter.
- Create a personal access token (opens in a new tab) with
repo
-only access. - Make a copy of the provided
/repo.template.json
file and rename it/repo.json
. This copy will contain secrets (so is git-ignored). Add the following:
owner
: your GitHub username.repo
: name of the new registry repo.token
: your personal access token.mainBranch
: name of the default (master) branch in your repo; GitHub defaults tomain
, but you can customize that.registryPath
: a directory path to where versioned schemas will be placed in the repo.
2. Development server & initial schema commit
With that done, you're ready to start development
mode:
yarn start-development
The following services are available for interactive queries:
- Development gateway: http://localhost:4000/graphql
- Inventory subservice: http://localhost:4001/graphql
- Products subservice: http://localhost:4002/graphql
Visit the gateway, and note that you have some development-only mutations available:
createSchemaReleaseBranch
updateSchemaReleaseBranch
createOrUpdateSchemaReleaseBranch
mergeSchemaReleaseBranch
Run createSchemaReleaseBranch
to build a release candidate:
mutation {
createSchemaReleaseBranch(name: "initial-release") {
name
sha
url
pullRequestUrl
}
}
The name
argument is a GitHub branch name for this release candidate. Running that mutation, you should get a response that includes the pullRequestUrl
of a newly-created PR in the remote repo containing the initial schema version (if you don't get a link, check your repo config). Review the PR on GitHub, then merge it! You can also get fancy and merge it using a local mutation:
mutation {
mergeSchemaReleaseBranch(name: "initial-release") {
name
sha
}
}
3. Production server
Now that you've comitted an initial version of your schema to the registry repo, you're ready to start the production server (the initial commit must be made first so that your production server has a schema to load).
In a second terminal window:
yarn start-production
The following services are now available for interactive queries:
- Production gateway: http://localhost:4444/graphql
- Development gateway: http://localhost:4000/graphql
- Inventory subservice: http://localhost:4001/graphql
- Products subservice: http://localhost:4002/graphql
4. Hot-reload version releases
You'll notice that the production server is polling the registry repo for version changes. Let's give it something to find... go into the Products subservice and add a new dummy field to the Product
type:
type Product {
...
dummy: String
}
Now commit those changes to a release branch:
mutation {
createOrUpdateSchemaReleaseBranch(name: "incremental-release") {
name
sha
url
pullRequestUrl
}
}
Try adding another dummy field to the schema, and then run the above mutation again to add the additional change into the release branch. You can even make changes to both subservices and stage them together for release.
With all changes staged in your PR, watch the production
gateway terminal logs and then merge the release PR (via GitHub UI or mergeSchemaReleaseBranch
). You should see the production gateway cutover on the next polling cycle (remember, you'll still need to reload GraphiQL to bring the revised schema into the UI):
version 1607572592741: 9d2a592013beddcca779e58d5f8fbb6930434ef4
version 1607572592741: 9d2a592013beddcca779e58d5f8fbb6930434ef4
VERSION UPDATE: d2709f88d9f60994d6d248902b8183534d4715a9
version 1607572603563: d2709f88d9f60994d6d248902b8183534d4715a9
Without polling
Polling is by no means necessary to trigger gateway schema reloads. An even simpler solution is to setup a dedicated mutation that reloads the gateway schema, and then call it manually or in response to deployment hooks. Try running the _reloadGateway
mutation in this example to manually trigger a reload:
mutation {
_reloadGateway
}
Of course, this sort of reload trigger should not be exposed in public APIs. You should remove this sort of utility from public-facing schemas, or use a separate non-GraphQL endpoint (such as a REST service) to trigger the action.
Summary
There's a lot to be said (opens in a new tab) about schema versioning and release strategies. Let's be (fairly) brief and distill a few key points:
Post-deploy hooks don't fix everything
Say you dilligently call mergeSchemaReleaseBranch
(or a similar release command) from your subservice's post-deploy hook... Neat! However, there will still be latency between that release and the next gateway polling interval. Even if you're not polling, post-deploy hooks probably won't time perfectly with your subservice(s) starting up, especially when deploying many instances. Long story short: there will always be latancy.
Therefore, you may find that post-deploy hooks aren't a magic bullet for orchestrating seamless schema rollouts. Whether the revised gateway schema rolls out in seconds (thanks to post-deploy hooks), or minutes (until you press a merge button by hand), either scenario presents a window in which conflicting schema errors may occur. That said, releases really boil down to either being non-breaking or breaking; the later of which needs a maintenance window or a deeper strategy.
Zero-downtime rollouts follow a basic formula
The best release strategies will always deploy updated subservices quietly behind the gateway proxy layer in a way that activates new features without breaking existing ones. Following this pattern, it doesn't really matter if a gateway schema rollout takes seconds or minutes after a subservice deploy:
- Deploy all updated subservices while keeping existing subservice features operational.
- Push all updated subservice schemas to the gateway as a single cutover.
- Decommission old subservices, and/or outdated subservice features.
Versioning subschemas with gateway code is a neat idea
Keeping your subschemas and gateway code versioned in proximity of one another using a shared repo or submodules actually solves a lot of problems:
- Test coverage can be tailored to your application design; subschemas are composed together and run integration tests using your real application code, versus relying on a representational gateway test harness.
- The production gateway may start itself up using a local copy of subschemas, versus relying on the availablity of a remote registry. In the event of a registry outage, the gateway is only ever blocked from performing live updates. It can always be redeployed and restarted safely.
- Development environments can be run with only select subservices running (this is extremely important as your service cluster grows). The rest of the gateway schema can then be filled in with mocked subschemas from the local registry.