Maintain & Evolve a Public GraphQL API

11/12/2019

 API DAYS @Paris

Aurélien David - @spyl94

collective intelligence driven civic start-up that develops participatory applications.​

🇫🇷 Le Grand Débat National

granddebat.fr

 + 1,9 M

Contributions

 

+ 2,7 M

Visitors

 

Public GraphQL API

An API is a contract with our users!

😀Evolve the contract 

For this, our technical team needs:

 

  • Flexibility
  • Freedom
  • Speed 

Bring new features and improvements

😨 Maintain the contract

Do not change :

 

The body of the responses

Query parameters

Available features

Do not break existing user integrations

Version Management

For users, every upgrade is a major upgrade. 🚧 

Problem: You can not make any significant changes after publication. 😱
 

  • /api/v1
  • /api/v2
  • /api/v3

We must therefore claim to have a perfect API for each version, which is hard ...
 

Version Management

Continuous evolution

We modify our API gradually, adding new features and deprecating usages. 

It's much closer to our development model ... We do not rethink our model every year, it evolves little by little. 

Handling BCs

Choosing a policy

Facebook's GraphQL schema (over 4 years old, 1,000s of types at this point, and under active change by 100s of engineers) has never needed a versioned breaking change, and still supports 4-year old shipped versions of iOS and Android apps (which unfortunately are still being used).

 

At Facebook they never break the schema ... 😱

Ok, but it's an internal GraphQL API ...

We'll announce upcoming breaking changes at least three months before making changes to the GraphQL schema, to give integrators time to make the necessary adjustments. Changes go into effect on the first day of a quarter (January 1st, April 1st, July 1st, or October 1st). For example, if we announce a change on January 15th, it will be made on July 1st.

At GitHub they depreciate and then remove after 3 to 6 months. 🏄

A good compromise for a public API.

Communicating on BCs

Changelog ~ Twitter ~ RSS

Field `votesCount` will be removed. Use `votes.totalCount` instead. In preparation for an upcoming change to the way we expose counters, this field will only be available inside a connection. Removal on 2019-01-01 UTC.

Include useful information ... Why? How to replace? When? 

Take care of the depreciation messages:
 

Do not just communicate about BCs 😜

Encourage your users to use the new features, it is giving them an interest to no longer use depreciated parts! 

Avoiding BCs

We will see together several means of prevention ...

But not a miracle method (sorry)!

 

Prevention #1

Design your schema for evolutions !

It's easier to add than to edit.

Adding new fields and types will not impact existing queries ✌️

With a REST (classic) approach we increase the payload of all customers 😞
 

Adding a new field is the easiest way to avoid a BC. 

Name things well

Sometimes being specific can save us later. (ex: AddVoteContributionEvent vs VoteEvent)

🤔 Just deprecating for a name is a blocking change that does not give value to customers, avoid it at all costs. 

There are only two hard things in Computer Science: cache invalidation and naming things.

Phil Karlton

Tip: Limit the scalars to the strict minimum.

type Event {
   name: String
   startTime: Date
   endTime: Date
}
type TimeRange {
    start: Date
    end: Date
}

type Event {
   name: String
   time: TimeRange
}

Do not hesitate to complicate
 

Is a scalar field enough, a field type would not be better?

Use the Relay specification

The main business object types must always implement Node.

Always check if the list type fields should be paginated or not. If yes, make a Connection. 

Each mutation must have its own type Input and Payload. 

It is designed for evolutions:

Nullable vs Non-nullable

Using nullable is a way to have a stronger schema! 

Don't do it : it's annoying for the customer who has to check "null" everywhere and your schema loses its meaning.

If everything is non-nullable the slightest error generates a snowball effect ...

Train your team

Get inspiration from existing public API.

  (and even their mistakes!)

Prevention #2

Avoid accidental breaking changes ...
 

Save the schema with the code.

✅ We make sure that the schema is up to date with each commit.

Spot blocking changes on the schema

Expliquer le problème

Continuous integration fails !

✌️ No risk to merge a Breaking Change!

Use it, too!

Prevention #3

Manage cases where we are not quite sure ...

Move a type or field from the Internal schema to the Public

How to handle cases where we are not quite sure?

It's normal not to be sure.

 

Let's be transparent.

 

Trust in the design of its API takes time.

 

We can allow ourselves small mistakes. 😉

  

Public

Internal

Preview

The use of the Preview schema makes it possible to provide functionalities without being constrained by the BCs policy. 

Divide our API into schemas

Obviously the goal is that everything that goes through Preview arrives as is in Public.

  

Prevention #4

Some GraphQL magic !

Who still uses the depreciated field?

How often has the field been used lately?

Just before deleting ...

Query

Analytics

query {
  consultations {
     title
  }
}

No magic but a little analysis... 😉

Type: Query
Field: consultations
Client: 1234

Type: Consultation
Field: title
Client: 1234

Know the usage of your schema

This is the key to trusting in the continuous evolution of your API! 

GraphQL allows it because it is the client who describes the needs. 

Example of what was developed at GitHub.

How often has the field been used lately?

Hey [my-api-client] we notice that you are using our deprecated field `votesCount` on type `Consultation`. This field will be removed in 2 weeks, please use `votes.totalCount` instead !

🚀 Generating a custom message

Who still uses the depreciated field?

Presentation inspired by the work done by

Marc-André Giroux and others at GitHub and Shopify.

  

Thanks !

Aurélien David - @spyl94