Slides from presentation, I've made on the BuildStuff LT 2018. Here I'm talking about issues, many people have found when using RESTful APIs and how GraphQL addresses them. Also I'm trying to cover the tradeoffs made by the standard, solutions proposed by different implementations and some ideas for the future.
3. REST: what is it all about...
... what problems does it have...
... and how GraphQL aims to solve them...
... at what cost...
... what can we do with it now ...
... and what could we hope for in the future.
AGENDA
4. what is it all about...
resources ≠ operations
CRUD over HTTP Methods
utilization of HTTP status codes
stateless
9. UNDER
FETCHING
async function getPostWithComments(postId) {
var post = await http.get('/posts/{postId}');
/* GET /posts/4277
{
"id": 4277,
"authorId": 345,
"title": "REST - why we cannot have nice things?",
"content": "a very long text"
}
*/
post.comments = await http.get('/posts/{postId}/comments');
/* GET /posts/4277/comments
[]
*/
return post;
}
10. OVER
FETCHING async function getPostTitle(postId) {
var post = await http.get('/posts/{postId}');
/* GET /posts/4277
{
"id": 4277,
"authorId": 345,
"title": "REST - why we cannot have nice things?",
"content": "a very long text"
}
*/
return post.title;
}
11. A (DOUBTFUL)
SOLUTION
Works in limited scope
Non-trivial to make it work right
Risky (you may accidentally expose more than desired)
/user/{id}/posts?include=comments
12. SUMMARY
not quite stateless
not quite a standard
strictly limited to HTTP request/response
schema-less
not self-descriptive
hard to correlate entities from different
endpoints
versioning
under and overfetching
17. GraphQL is a query language for APIs and a runtime for
fulfilling those queries with your existing data.
GraphQL provides a complete and understandable
description of the data in your API, gives clients the power
to ask for exactly what they need and nothing more, makes
it easier to evolve APIs over time, and enables powerful
developer tools.
20. HOW DOES
IT LOOK
LIKE?
Query:
query {
article(id: ”1337”) {
id
title
author {
...fullName
}
created
comments {
author {
...fullName
}
created
}
}
}
fragment fullName on User {
firstName
lastName
}
Result:
{
"id": “1337”,
"title": "Horse steroids - guide for athletes",
"author": {
"firstName": "Andy",
"lastName": "Greenberg"
},
"created": "2015-05-22T14:56:29.000Z",
"comments": [
{
"author": {
"firstName": "Sam",
"lastName": "Gonagal"
},
"created": "2015-05-22T14:56:34.000Z"
},
{
"author": {
"firstName": "Chris",
"lastName": "McDonald"
},
"created": "2016-05-22T14:56:34.000Z"
}
]
}
21. CONSUMER
SIDE
Query for readonly actions.
Mutation for anything that affects the state.
Subscription for continuous stream of data.
22. Ask for GraphQL schema using GraphQL
query language.
query AllTypesWithFields {
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
INTROSPECTION API
24. GRAPHQL
ON THE
CLIENT
SIDE
Relay
originally
developed in
Facebook
quite opinionated:
requires a separate
set of constraints
on top of your
GraphQL schema
thightly integrated
with React stack
fast!
Apollo
easier to learn
tons of plugins
easy to integrate
with other
technologies ie.
Redux or Angular
43. GRAPHQL Perfect use case
▪ Multilayered object tree domain model
▪ Low-bandwidth / high-latency networks (e.g. mobile)
▪ Public API for 3rd party consumers
I think that to understand GraphQL, the best way is to start from REST. Therefore we'll target some of the issues of REST and how GraphQL addresses them. However keep in mind: there's no silver bullet. For this reason, we also should understand the tradeoffs, that have been made, so in the future, you'll be ready to make concious decisions wheater to use it or not.
I theory, term REST has been created as part of the PhD thesis back in 2000. Somehow it happened to be used as an idea architecture pattern for modern day distributed web services. Why? It described few key properties, that are very appealing to us, developers.
1. It standarizes semantics of doing most common entity collection operations.
2. It's stateless by default, which is makes scalling a lot easier.
3. It's well established in HTTP, which nowadays is the most common communication protocol.
There's only one problem: how do you enforce this behavior? Calling developers of other endpoint idiots won't make the problem disapear. What if someone will use totally different convention?
Another problem is that the standard has some undefined behaviors in it. Example can be this Stack Overflow question. If you read about it, there is around dozen of proposed status codes. The worst part: many of them actually have sense.
Another problem, is that REST is clearly related to HTTP request/response protocol. Which begins to be a problem in modern highly responsive systems. Not only because we can no longer rely on HTTP methods or status codes, but also because conceptually WS fits more message- and operation-based communication model than resource-based.
While GraphQL is a query language, its created to be an application-level query language as opposed to a database-level query languages like SQL. That doesn't mean, databases cannot expose their API as GraphQL endpoints. In fact some of them like [ArangoDB](https://www.arangodb.com/2016/02/using-graphql-nosql-database-arangodb/) to exactly that, while other like Postgres make it possible by using plugins on top. Usually this may be a valid option for document or graph databases, because of its construction.
I don't know how about you, but I like to know that actual enpoint I have is constrained by the set of explicit rules rather than phase of the moon and a tempter of its developer. If some of you feel, that these constrains block your freedom of expression, then you probably started building your own protocols already.
What's worth to notice here:
The output data format is actually an object tree. It doesn't has to be JSON however it's fully JSON compatible. This is quite different from i.e. SQL, where you need additional mapping layer for your data tables - I'm talking about ORMs here.
We already can see, that while query format may resemble JSON, it's not actually a JSON. Syntax is fully fledged query language. We can define parameters, as well as so called fragments: a reusable pieces of data, which allow us to avoid code duplication (again in much nicer way than i.e. SQL).
Errors are part of message payload, rather than HTTP status code. This comes from a simple reason - GraphQL is not bound to HTTP protocol, it's fully agnostic. This means that (depending on the implementation) you can test entire GraphQL server fully in memory, without spinning any web host.
We don't need 4 or more different HTTP verbs. GraphQL specifies 2 major ones: queries for readonly requests, and mutations for all others. The major difference is processing model: since queries don't alter state, their execution can be easily parallelized, while mutation must be executed sequentially in case of interdependent operations being executed.
Subscriptions were not part of an original standard when it appeared, however over time they were specified and added by most of the popular implementations.
In result of this approach, usually all you need to know to get full knowledge of API is to have URL for its GraphQL endpoint. This also gives an immense power for tooling.
Before we continue, let me introduce you a little to how the F# implementation of GraphQL server works. It’s split in 3 phases:
Compilation phase, which happens when you’ve specified full domain of your GraphQL schema. At this point we already can optimize some cases.
When a user request arrives it consists of 2 things: GraphQL query and (optionally) set of variables used by this query. First we parse GraphQL query into abstract syntax tree and spread it by inlinening all of the fragments. Then we combine that input with the type system and resolvers described on the server side. This gives us full knowledge about, how to execute your request. Additionally this information can be passed to your resolvers, so they can be used by you to further optimizations.
Last we apply incoming variables to the execution plan to produce the results.
While it’s not a big problem in dynamic languages, which rely on their JIT optimizations, statically typed languages could take advantage of generating optimized GraphQL server code at compile time. We do that in F#, however it’s done in limited scope at runtime, which also slows server startup (making it less useful i.e. for Serverless). This compile time optimizations are already possible is gengql (Go implementation). They should be possible in C# by using Roslyn CodeGen, and I hope that when eventually TypeProviders over types will arrive to F#, we’ll be able to do the same.
There are several advantages of separating execution plan from actual query evaluation:
We can provide execution plan to you, when your logic is triggered, so you can make your code smater by leveraging the full knowledge about executed query.
You can decide to cache that execution plan, so that queries, which are executed often, won’t need full evaluation on every call.
In the future, we could make further optimization by implementing runtime compilation of execution plan, making it basically a single compiled function. At this point GraphQL requests could be potentially as fast as the web service actions your write by hand.
REST APIs are literally like giving your business domain to consumer in a little puzzle pieces.