The schema is the contract

GraphQL, from zero to governance — part 2

The one document both sides have to agree on, and the reason arguments about it get so heated.

This primer is for anyone who wants to read a schema when one shows up in a ticket. It assumes the framing from part 1, but doesn’t cover writing one.

If writing APIs is something you do regularly, you’ll want to skip ahead to part 4, “Something has to actually go get it” (coming soon).


Back to the screen

Remember that product page from part 1? It had these things on it: the photo, name, description, price, a discount badge, “3 left in stock,” a star rating with its 412 reviews, a delivery estimate, and a line that says deliver to your saved address.

Now look at just one of them. The price.

For that number to reach the screen, something somewhere had to agree, ahead of time, that a thing called price exists and that it holds a float (a number that can have a decimal), not a sentence, not a color. If the app asks for price, then the server needs to know what price means ahead of time. Neither can be guessed.

It applies to everything. Every item on the page is a small agreement: this field exists, it goes by this name, and its value is this kind of thing. They can’t be made up on the spot when you loaded the page.

Part 1 asked where each of those things comes from and who decides what it’s called. Half of that question — what exists, and what it’s named — has an answer you can read for yourself.

That answer is the schema.

The product page as a schema

I am going to write out the page as a list first.

Start at the top. The photo isn’t the image itself because a schema doesn’t hold pixels. It holds an address where the image lives, so the photo comes through as a piece of text: a URL. The name is text. The description is text.

The price is a number. The discount badge is a number too, a percentage, except plenty of products don’t have one. That field is allowed to come back empty and there is logic around that as well. “3 left in stock” is an Int (keep in mind that it is probably just pulling the number and inserting it into the left in stock sentence). The star rating is another float, because 4.6 stars is a real thing a product can have.

Then you have the reviews, and they behave differently from everything above them. A review isn’t a value. It has its own rating and author, so the reviews field can’t hold just text or just a number. There are 412 of them on this page, and each one is a separate thing with a score and name attached. A list is the only shape that fits. So the reviews field holds a list of reviews, where each review is its own little object with its own fields. The tree keeps going for one more level, and then it stops.

The delivery estimate is text. The saved address is text.

That’s the page, in words. Here it is as a schema:

type Product {
  name: String!
  description: String
  imageUrl: String!
  price: Float!
  discountPercent: Int
  stockRemaining: Int!
  rating: Float
  reviewCount: Int!
  reviews: [Review!]!
  deliveryEstimate: String
  savedAddress: String
}

type Review {
  rating: Int!
  author: String!
}

This is the SDL, or Schema Definition Language.

The schema tells you nothing the page didn’t already tell you, it’s just written down so a machine can check it.

There’s one of these. Not a copy for the app and a separate copy for the server. One of them, and both teams build against it. That single shared copy is what makes everything later in this post possible.

+-----------------------------------------------+
|  +---------+                                  |
|  |         |   Field Runner 3                 |  <- name
|  |  photo  |   Waterproof trail running shoe  |  <- description
|  |         |                                  |  <- imageUrl
|  +---------+   $129     15% off               |  <- price / discountPercent
|                                               |
|   3 left in stock                             |  <- stockRemaining
|   * * * * .   4.6    (412 reviews)            |  <- rating / reviewCount / reviews
|                                               |
|   Get it by Thu, Aug 20                       |  <- deliveryEstimate
|   Deliver to your saved address               |  <- savedAddress
+-----------------------------------------------+

The product page from part 1, each region labeled with the schema field behind it. Still uncolored: which team owns which field is a later post.

Types, fields, and where the tree stops

Every field in the schema has a type. The type answers one question: what kind of thing is this value?

Some types are just values. String is text. Int is a whole number. Float is a number with a decimal, which is why the star rating is a Float — 4.6, not 4. These are the end of the road. When a field is a String, you’ve hit the bottom — there’s nothing to open up, nothing left to ask.

The word for a type where the tree stops is a scalar.

Other types are objects, and objects have fields of their own. reviews is a list of Review, and a Review has a rating and an author. That’s a branch and it allows you to keep going.

The word for one you can keep opening is an object type.

A schema is a tree of object types, and scalars are where every branch finally ends.

This is most of what you’re doing when you read a schema to find out what an API holds. You scan for the field you want, and its type tells you whether you’ve arrived or if there’s another object hanging off it and even more to ask for. To pull a reviewer’s name out of this schema, you’d start at Product, step into reviews, and read author off a Review. Three hops, and the schema is the map for every one of them.

Important – The ! after a type — String! — tells you that the field is required to be there, never empty; a plain String with no ! can come back missing, and the app should be prepared for that.

Why it’s a contract, not documentation

Documentation describes something. You can ignore documentation (as someone whose previous role was technical content writer, you shouldn’t. But you can). You can let it go stale, contradict it, ship code that does the opposite, and the docs just sit there being wrong. Nothing enforces them.

A schema is checked by machines. The moment the server starts returning price as text instead of a float, or drops a field the schema promised, it fails. Both are bound to the same document, and if it changes underneath either of them, their code breaks.

Documentation is a note about the system, but the schema is an integral part of the system.

Renaming a field in a doc is a typo fix. Renaming a field in a schema can break every app that asked for the old name. It’s an artifact the client and server team both have to sign, so it’s the one artifact no single team gets to change on its own.

(What happens when someone does want to change a signed contract is a whole thing. More on that later.)

The moment it stopped being a Facebook thing

None of this is Facebook-specific, even though part 1 started there. Give it a year and you can watch a completely different company arrive at the same place.

On September 14, 2016, GraphQL left technical preview. The same day, GitHub published a note explaining why it was rebuilding its API — a REST API that a huge number of developers used every day — on GraphQL instead.

“It seemed like our responses simultaneously sent too much data and didn’t include data that consumers needed.”

— GitHub Engineering, “The GitHub GraphQL API”, September 14, 2016

It was too much and not enough, at the same time. How does that work? It’s what happens when the server decides the shape of every response and the client just takes whatever shows up. GitHub wanted an agreed-on description of every type and field, so a client could ask for exactly what it wanted and know exactly what it would get back.

Facebook built GraphQL for News Feed. GitHub adopted it for an API that a huge number of developers touch.

The @ symbols you’ll see

One thing turns up in real schemas that isn’t in ours: annotations that start with an @. @ this, @ that, stuck onto a field or a type like a sticky note.

Those are directives, but it’s outside the scope of this blog and it doesn’t change how you read the fields. We’ll get to it later on.

For now, when you see an @ in a schema, just clock it: that’s a directive, a note to the machinery rather than to you.

What the schema won’t tell you

So now you know what you’re looking at. You can walk the types, and say what data lives behind an API without asking an engineer.

But did you notice that the schema promises price exists and that it’s a float, but it says nothing about where that comes from.

[ screen ] ──── schema ──── ??? ──── [ data ]
                   ^         ^
             you are here part 4

The series diagram, state 2. The schema box is in place. Part 3 covers how the screen asks the schema for what it needs; part 4 fills the gap between the schema and the data.

Those are two separate questions. Part 3, asking for exactly what you want, is how you request the fields you need. Part 4, something has to actually go get it, is who answers (coming soon).

Brendan Bondurant Avatar

About the author

More about Brendan · LinkedIn · GitHub


Comments

2 responses to “The schema is the contract”

  1. […] What a schema actually is — how it’s written, why arguments over it turn into standoffs, why it’s the one document both teams have to sign — is the next post in the series, “The schema is the contract”. […]

  2. […] wanted to know what they were looking at. This assumes you can read a schema, which is covered in part 2, the schema is the contract. It doesn’t cover mutations or subscriptions in depth, or how to run any of this in […]

Leave a Reply

Discover more from Brendan Bondurant

Subscribe now to keep reading and get access to the full archive.

Continue reading