Heads up: I’m going to assume familiarity with common GraphQL terms on your part, like schemas and resolvers. If you need a refresher, I recommend checking out GraphQL.org
GraphQL represents a fundamental shift in how we developers can deliver features to end users faster than ever before. Having a decoupled schema, separate from both your client(s) and server(s) gives you a ton of flexibility when it comes to iterating on a contract for a feature. One useful technique that GraphQL enables is the zero-downtime migration, which I’ll attempt to illustrate with Data.
No, not data, Data, from Star Trek: The Next Generation.
Let’s imagine a schema we have for characters of the popular Sci-Fi show, which may look something like the following:
Which would allow us to query for characters like:
Which would yield:
Now, you’re probably wondering about that
isSentient field. The episode “The Measure Of A Man” centres on if Data should be considered sentient. The ensuing episode revolves around figuring out if Data is sentient according to the definition given by Maddox:
- Intelligence: the ability to learn, understand, and cope with new situations
- Self-awareness: being conscious of one’s existence and actions or aware of one’s self and one’s ego
Our schema cannot support this new, complex definition of sentience! Our current schema exposes it as a
Boolean, but (as Picard argues), Data fits some, but perhaps not all, of the conditions to be considered sentient.
I’m going to attempt to use this example to illustrate how we can take a schema-first approach to enable a zero-downtime migration from our current schema to something more complex.
For this example, I want you to imagine that we want to build out a more robust system that can determine if a character is sentient. Our existing system returns
true/false, but we need to change it to return how a given character fits into any of Maddox’s given criteria for sentience. Building this system out fully may take a number of weeks, with the client having to wait until the server team is finished before they begin their implementation.
Leveraging the schema-first development style and some resolver magic, I want to illustrate how our teams could work on the client and server for this feature simultaneously, allowing either side to ship code to our users at a rate that works for them, without getting stuck on a dependency to another team. In this way, our client could be finished, running out in production, before the server team is “done” implementing the feature.
First, we’ll need to define the schema for our new rules we will use to judge sentience:
Okay, I’ve extended our
Character type to have a new, more complex attribute defining their sentience, called the same, beyond a simple
sentience returns an array of
SentienceCondition, which will (at the time of Data’s trial) be “Intelligence”, “Self-awareness”, and “Consciousness”. Our implementation can be hard-coded to return these three conditions, mapping
isSentient to the
Character->sentience might look like the following:
Let’s update our operation that fetches Commander Riker’s data so we’re still on the same page:
Which now yields:
We can see our three
SentienceConditions all have
satisfies = true, as this is now an abstraction over the old
This is huge! You can see that writing this thin abstraction over the current
isSentient flag would not be much work server-side. However, this would allow any of our clients to start using our new
sentience feature right away. If we have a schema registry set up with Apollo (for instance), client developers can use the Apollo GraphQL VSCode extension to update their operations/unit tests/prop types/Typescript types, all without leaving Visual Studio Code.
With a minor modification to our schema, and a quick hard-coded implementation based on our previous work, our clients can start consuming data in the new shape we want before we write a single line of code for our new feature! This helps us get around the waterfall-like nature of typical client/server development, where the server must be 100% ready before the client starts consuming from it.
Following the schema-first approach, we can unburden teams from heavy reliance on one another through an agreed-upon contract: our GraphQL schema. Once the schema is “live”, both teams can be reasonably confident it will be stable and can start coding towards it. In a zero-downtime migration scenario, we can write a thin abstraction over our old implementation, have clients implement the new schema, and remove the dependency that often exists between the two sides.
Oh, right, Data! Let’s see what the response for him looks like:
Yields… well, you’ll have to watch the episode!
- GraphQL Schema Design: Building Evolvable Schemas
- Shopify’s GraphQL Tutorial: Designing a GraphQL API
- GraphQL Schema Design @ Scale (presented by Marc-André Giroux)
- Battle-Hardened API Patterns from Two Years in Production (presented by Kiyan Azarbar)