Using feature flags in GraphQL

I’ve been using GraphQL in some form or another for a couple of years now, and I have… opinions about it. Opinions notwithstanding, it’s a supremely flexible and powerful tool, and I just want to highlight something here that it seems quite a few people don’t know about.

That something is Directives, which is a way of optionally including fields. But why is this useful?

Scene of the crime

Let’s imagine that you’re working on a new feature on a well established app. This app, for a contrived example, shows a list of types of bird. It’s a scrollable list of carousels, with each item showing a name of a bird under a header. For whatever reason, the GraphQL you use to fetch this information looks like this:

query GetBirdTypes() {
    falcons {
        name
        image
        prey
    }
    crows {
        name
        image
        lifespan
    }
    # etc
}

Sadly, your designer isn’t very good

The app is doing well, making tonnes of money. Who doesn’t love birds. Product decides that you need to support hummingbirds as a new category; it’s migration season for the Ruby Throated Hummingbird, and you need to display when the migration is likely to be in your area.

Easy, you say to yourself. Backend have provided a new type, hummingbirds, and you dutifully add it to your query:

query GetBirdTypes() {
    # ...
    hummingbirds {
        name
        image
        migrationTime
    }

“Job done”, you say to yourself, turning your attention to the nectar feeder in the yard. It is migration time after all.

Unfortunately, there’s a problem. As a consumer of GraphQL, you have no idea what’s happening under the hood, and it turns out that the GraphQL server has to make a request to a third-party server specifically about hummingbirds when this new field is added. This server is having a bad day and timing out, which causes the entire query to fail. Users can’t see any birds. This is bad.

Because you’re a responsible engineer, you feature-flagged all the new code, and you disable it. However, the problem doesn’t actually lie in that code - rather with the backend itself, and turning off the feature does nothing, because ultimately you’ve modified a query that is used elsewhere. The home feed team is angry at you, and you’re sad.

This is the insurance that directives offer - you can optionally choose to query new fields, and therefore cut out new, risky paths in your GraphQL queries and guard against a new type of failure which is otherwise largely out of your hands.

Enter Directives

Your day would have been saved if you had done something like this:

query GetBirdTypes(
    $includeHummingbirds: Boolean = false
) {
    # ...
    hummingbirds @include(if: $includeHummingbirds) {
        name
        image
        migrationTime
    }
}

And all you have to do is pass your feature flag to the query, presumably you’d be using Apollo:

suspend fun getBirdTypesList(): Result<BirdTypes> {
    val response = apolloClient
        .query(
            GetBirdTypesQuery(
                includeHummingbirds = isMyFeatureFlagEnabled
            )
        )
        .execute()
    // ...
}

If you want the inverse of this behaviour, you can instead use @skip(if: Boolean).

Also note that GraphQL supports default values! So you can reduce the blast radius further, and teams re-using this query who don’t care about hummingbirds (people who are wrong) don’t have to do anything at all.

You can also use these in Fragments (not those Fragments):

fragment Hummingbirds {
    name
    image
    migrationTime
    call @include(if: $includeBirdCall) {
        url
        length
    }
  }
}

Just make sure that the query (or mutation) utilising this Fragment also takes a parameter includeBirdCall.

Closing Thoughts

This technique has saved many a bacon, and it can save yours too. If you use GraphQL at any scale, consider using Directives next time you add a field to an existing query. It’s super easy to do and a great example of the flexibility of GraphQL. Thanks for reading.

comments powered by Disqus