Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Overview of GraphQL & Clients

Pokai Chang
November 29, 2017

Overview of GraphQL & Clients

Pokai Chang

November 29, 2017
Tweet

More Decks by Pokai Chang

Other Decks in Technology

Transcript

  1. My Background ⬢ Building stuff as a web developer from

    2012 ⬢ Shallow experiences covered from design, mobile, front-end and backend develop to cloud deployments (AWS) ⬢ Fan of GraphQL/Relay of its beauty of API design since 2015 ⬢ Working at with Ruby, JavaScript (React.js) and playing Elixir ⬢ Former tech lead at Colorgy ! # @zetavg
 fb.me/pokaichang72
  2. ⬡ Complain about RESTful Introduce GraphQL ⬡ Just enough GraphQL

    to get started ⬡ GraphQL client library overview ⬡ Intro to Relay ⬡ Demo: GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC Outline
  3. The evolution of API ⬢ RESTful: Easy to use, easy

    to develop ⬡ Directly based on Wide World Web ⬡ URI as resource name (noun), HTTP method as action (verb) ⬡ We need documents: Swagger... ⬡ ...and type definitions: JSON Schema ⬡ ...and data relations: JSON API ⬡ Combine them all: API Blueprint, RAML
  4. The evolution of API ⬢ But for the front-end, especially

    SPA or mobile apps: ⬡ Querying complex data efficiently is still hard ⬡ We may come up with lots of endpoint versions ⬡ Or messy features on different endpoints ⬡ On purpose specs are hard to follow, without an clear interface, APIs tends to be hard to reuse and maintain ⬡ Writing code to fetch and store data is annoying ⬡ Caching is hard cause there's no explicit schema ⬡ Co-working may be messy cause there's no schema
  5. GraphQL ⬢ A new query language ⬢ Brief History: ⬡

    2012 - Used for Facebook mobile app ⬡ 2015 - Publicly released ⬡ 2017 - Now: GraphQL & Relay re-licensed under 
     MIT ⬢ Normally uses a single endpoint URL ( POST /graphql )
  6. $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌,

    ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 }
  7. $ { } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~",

    "followers": [◌], "repos": [◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌] "repos": [◌, ◌]
 } ! { "n "d "s } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thing Compile "description": "...", "stargazers": [◌, ◌, ◌ } ! { "name" "descr "starg } { } { "viewer": ◌
 } $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ d d d d d d d d
  8. ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌,

    ◌]
 } ! { "name": "Thing Compile "description": "...", "stargazers": [◌, ◌, ◌ } ! { "name" "descr "starg } ! { "n "d "s } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } { } { "viewer": ◌
 } $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌] "repos": [◌, ◌]
 } $ { } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } $ $ $ $ $ $
  9. ! { "name" "descr "starg } ! { "name": "Thing

    Compile "description": "...", "stargazers": [◌, ◌, ◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "n "d "s } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } { } { "viewer": ◌
 } $ $ $ $ $ $ $ $ $ $ $ / $ { "name": "Lucy", "bio":"...", "followers": [◌] "repos": [◌, ◌]
 } $ { } $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 }
  10. $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌,

    ◌, ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 } GraphQL
  11. ⬡ Complain about RESTful Introduce GraphQL ⬡ Just enough GraphQL

    to get started ⬡ GraphQL client library overview ⬡ Intro to Relay ⬡ Demo: GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC Outline
  12. Basic Query ⬢ Starts with selecting fields on the query

    root ⬢ WYSIWYG { "data": { "viewer": { "name": "Pokai Chang" } } } query { viewer { name } }
  13. Basic Query ⬢ Querying nested fields { "data": { "viewer":

    { "name": "Pokai Chang", "birthday": { "month": 7, "day": 2 } } } } query { viewer { name birthday { month day } } }
  14. Types ⬢ Get the type of a object using the

    __typename meta field { "data": { "viewer": { "__typename": "User", "birthday": { "__typename": "Date" } } } } query { viewer { __typename birthday { __typename } } }
  15. Type defs as docs # GraphQL query language
 
 query

    { viewer { name birthday { month day } following { name } } } # GraphQL schema language
 
 type Query { viewer: User } type User { name: String!
 birthday: Date
 followers: [User] following: [User] } type Date { year: Integer month: Integer day: Integer }
  16. Non-Null & Lists # GraphQL schema language
 
 type Query

    { viewer: User } type User { name: String!
 birthday: Date
 followers: [User] following: [User] } type Date { year: Integer month: Integer day: Integer } [<thing>] means an array of <thing> objects ! means that the field is non-nullable
  17. Arguments ⬢ Nested fields also can have arguments query {

    user(id: 1) { name repo(name: "awesome-graphql") { name description } } }
  18. Variables ⬢ A way to dynamically change arguments for fields

    query ($userId: Int!, $repoName:String!) { user(id: $userId) { name repo(name: $repoName) { name description } } } {
 "userId": 1, "repoName": "awesome-graphql" } +
  19. Fragment fragment profileFields on User { name bio avatarUrl }

    query { viewer { ...profileFields } user(id: 1) { ...profileFields } } Pre-define a set of fields
 on a type or interface as meaningful fragment
  20. Interfaces ⬢ An abstract type that includes a set of

    fields that a type must define to implement ⬢ Can be used for fragments interface Actor { id: ID! name: String! avatarUrl: String! } type User implements Actor { id: ID! name: String! avatarUrl: String! ... } type Bot implements Actor { id: ID! # Sample Query fragment actorFields on Actor { name bio avatarUrl } query { feed { actor { ...actorFields }
 verb

  21. Mutate Data w/ Mutations ⬢ Mutation queries lives under mutation

    instead of
 query , and are ways how we can change the data ⬢ We can put the input data in arguments, changed nodes will be returned in the selectable payload ⬢ It’s a convention like RESTful GET/POST that clients rely on mutation { addComment(input: { subjectId: 1, body: "Hi." }) { subject { comments { body } } } }
  22. Input Types ⬢ Yes, inputs are also typed input AddCommentInput

    { subjectId: ID! body: String! } mutation { addComment(input: { subjectId: 1, body: "Hi." }) { subject { comments { body } } } }
  23. Query tree ⬢ Each query is a tree extracted from

    the graph ⬢ The query is resolved by traversing the tree and resolving each field query { viewer { name
 bio repos { name
 description } } }
  24. $ { "name": "J "bio":"... "followers "repos": [ } $

    { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } ! { "name": "Awe "description "stargazers" } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] { } { "viewer": ◌
 } $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 } Query tree
  25. Query tree ! { "name": "Thing Compiler", "description": "...", "stargazers":

    [◌, ◌, ◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Handy Util", "description": "...", "stargazers": [◌]
 } { } { "viewer": ◌
 } $ { "name": "Pokai Chang", "bio":"Yet another geek.", "followers": [◌, ◌, ◌], "repos": [◌, ◌, ◌]
 }
  26. ⬡ Complain about RESTful Introduce GraphQL ⬡ Just enough GraphQL

    to get started ⬡ GraphQL client library overview ⬡ Intro to Relay ⬡ Demo: GraphQL & Relay on Rails
 https:/ /github.com/zetavg/RailsRelayTodoMVC Outline
  27. $ { "name": "Neson", "bio":"Yet another geek.", "followers": [◌, ◌,

    ◌], "repos": [◌, ◌, ◌]
 } $ { "name": "Lucy", "bio":"...", "followers": [◌], "repos": [◌, ◌]
 } $ { "name": "Ja "bio":"..." "followers" "repos": [◌ } $ { "name": "Pusheen", "bio":"Nyan nyan nyan~", "followers": [◌], "repos": [◌]
 } ! { "name": "Thing Compiler", "description": "...", "stargazers": [◌, ◌, ◌]
 } ! { "name": "Hello World", "description": "...", "stargazers": [◌, ◌]
 } ! { "name": "Handy Ut "description": ". "stargazers": [◌] } ! { "name": "Awes "description" "stargazers": } ! { "name": "Todo", "description": "...", "stargazers": [◌]
 } ! { } { "viewer": ◌
 } GraphQL
  28. Relay data fetching View $ $ $ $ $ d

    d $ $ $ $ d d d d d d Relay Store
  29. Relay data fetching viewer { name bio } View d

    d d d d d d d viewer { repos { name description } } $ Relay Store
  30. Relay data fetching View d d d d d d

    d d viewer { repos { name description } } Backend query { viewer {
 name
 bio repos { name description } } } $ Relay Store viewer { name bio }
  31. Relay data fetching View d d d d d d

    d d viewer { repos { name description } } Backend { "data": { "viewer": {
 "name": "…", "bio": "…", "followers": […], "repos": […] } } } $ viewer { name bio } query { viewer {
 name
 bio repos { name description } } }
  32. Relay data fetching View $ ㄎ ㄎ ㄎ ㄎ viewer

    { repos { name description } } d d d d d d d d viewer { name bio }
  33. View Relay data fetching viewer { name bio } View

    $ ㄎ ㄎ ㄎ ㄎ viewer { following { name } } d d d d d d d d $ $ $ $ $
  34. View Relay data fetching viewer { name bio } View

    $ ㄎ ㄎ ㄎ ㄎ viewer { following { name } } d d d d d d d d $ $ $ $ $ Backend query { viewer { following { name } } } { "data": { "viewer": { "following": […] } } }
  35. View Relay data fetching viewer { name bio } View

    $ ㄎ ㄎ ㄎ ㄎ viewer { following { name } } d d d d d d d d $ $ $ $ $ ㄎ ㄎ ㄎ ㄎ
  36. Query tree query { user(login: "zetavg") { name repositories {

    name } } } { "data": { "user": { "name": "Pokai Chang", "repositories": [ { "name": "dotfiles" }, { "name": "Thing" }, { "name": "Stuff" } ] } } }
  37. Query tree Query Root User Repo Repo Repo name user(login:

    "zetavg") "Pokai Chang" "dotfiles" name repositories name "Thing" name "Stuff" query { user(login: "zetavg") { name repositories { name } } } { "data": { "user": { "name": "Pokai Chang", "repositories": [ { "name": "dotfiles" }, { "name": "Thing" }, { "name": "Stuff" } ] } } }
  38. Caching the query result ⬢ Strategy 1: traverse path Query

    Root user(login: "zetavg") User Repo Repo Repo name "Pokai Chang" "dotfiles" name repos name "Thing" name "Stuff" ⬡ Same path, same object
  39. ⬢ Strategy 1: traverse path Query Root user(login: "zetavg") User

    Repo Repo Repo name "Pokai Chang" "dotfiles" name repos name "Thing" name "Stuff" user(login: "zetavg") user(login: "zetavg")/repos[2] ⬡ Same path, same object Caching the query result
  40. ⬢ Strategy 1: traverse path Query Root user(login: "zetavg") User

    Repo Repo Repo name "Pokai Chang" "dotfiles" name repos name "Thing" name "Stuff" repo(owner: "zetavg", name: "dotfiles") Repo name "dotfiles" ⬡ Sometimes path assumption isn’t enough Caching the query result
  41. ⬢ Strategy 1: traverse path Query Root user(login: "zetavg") User

    Repo Repo Repo name "Pokai Chang" "dotfiles" name repos name "Thing" name "Stuff" repo(owner: "zetavg", name: "dotfiles") Repo name "dotfiles" Same object on different path ⬡ Sometimes path assumption isn’t enough Caching the query result
  42. ⬢ Strategy 1: traverse path ⬢ Strategy 0: object identifier

    repo/dotfiles Repo name "dotfiles" repo/dotfiles Repo name "dotfiles" Query Root User Repo Repo user(login: "zetavg") "Pokai Chang" name repos name "Thing" name "Stuff" repo(owner: "zetavg", name: "dotfiles") repo/Thing repo/Stuff Caching the query result
  43. ⬢ Strategy 1: traverse path ⬢ Strategy 0: object identifier

    Query Root User Repo Repo user(login: "zetavg") "Pokai Chang" name repos name "Thing" name "Stuff" repo(owner: "zetavg", name: "dotfiles") repo/Thing repo/Stuff Repo name "dotfiles" repo/dotfiles Caching the query result
  44. ⬢ Strategy 1: traverse path ⬢ Strategy 0: object identifier

    ⬡ Relay: we need the server to give a global id for nodes that need to be identified ⬡ Apollo: client defines a dataIdFromObject function that will be executed on every node ⬡ Fun fact: Relay stores each object it fetched in a key-value store with the object id or traverse path as key, any field that contains an object will actually be the key of the object, so two objects having the same id will be ensured the same by Implementation Caching the query result
  45. ⬢ Offset based pagination, e.g.: per_page=5&page=1 ⬢ Cursor? page 1

    page 2 page 3 1 2 3 4 5 page 1 Client fetches page 1
  46. ⬢ Offset based pagination, e.g.: per_page=5&page=1 ⬢ Cursor? page 1

    page 2 page 3 ' Broken page 1 page 2 page 3 1 2 3 4 5 page 1 Data inserted
  47. ⬢ Offset based pagination, e.g.: per_page=5&page=1 ⬢ Cursor? page 1

    page 2 page 3 ' Broken page 1 page 2 page 3 1 2 3 4 5 page 1 page 2 5 6 7 8 9 ' Client got malformed results
  48. ⬢ Offset based pagination, e.g.: per_page=5&page=1 ⬢ ⬢ Cursor based

    pagination, e.g.: after: "…", next: 5 Cursor? next 5 next 5 page 1 page 2 page 3 ' Broken page 1 page 2 page 3
  49. Relay Connections ⬢ The design of Relay Cursor Connections query

    { viewer { friends(first: 10, after: "someCursor") { edges { cursor node { id name } } pageInfo { hasNextPage } } } } Edge (UserEdgeType) Node (UserType) { … } Cursor Current cursor Connection Edges Edge (UserEdgeType) Node (UserType) { … } Cursor Edge (UserEdgeTy Node (UserType { … } Cursor Page Info Starting cursor
  50. Mutations ⬢ A mutation is a query that has side

    effects ⬢ The changes made on the graph will be put on the response, the client is responsible to select the necessary parts mutation { renameRepo(input: { repoID: "…", name: "NewName" }) { repo {
 id name } } } Grab the changes that are
 made on the existing repo
  51. Mutations ⬢ A mutation is a query that has side

    effects ⬢ The changes made on the graph will be put on the response, the client is responsible to select the necessary parts ⬢ In general, we need to write an updater function to update the store with the payload:
 
 (oldState, payload) => newState ⬢ Relay and Apollo both has some conventions ⬡ Objects with matching identifier in the store will be updated automatically
  52. Mutations UI $ ㄎ ㄎ ㄎ ㄎ d d d

    d d d Mutation Server Store
  53. Mutations UI $ ㄎ ㄎ ㄎ ㄎ d d d

    d d d Mutation Server Updater Store Response
  54. Mutations UI $ ㄎ ㄎ ㄎ ㄎ d d d

    d d d Mutation Server Updater Store Response
  55. Mutations UI $ ㄎ ㄎ ㄎ ㄎ d d d

    d d d Mutation Server Updater Store Latency Response '
  56. Optimistic Update UI $ ㄎ ㄎ ㄎ ㄎ d d

    d d d d Mutation Store Latency
  57. Optimistic Update UI $ ㄎ ㄎ ㄎ ㄎ d d

    d d d d Mutation Optimistic Updater Store Latency Optimistic Update Layer
  58. Optimistic Update UI $ ㄎ ㄎ ㄎ ㄎ d d

    d d d d Mutation Optimistic Updater Server Updater Store Latency Response Optimistic Update Layer
  59. Optimistic Update UI $ ㄎ ㄎ ㄎ ㄎ d d

    d d d d Mutation Optimistic Updater Server Updater Store Latency Response
  60. GraphQL Live Query ⬢ Idea: after the client sends a

    query, server can push updates of the query result to the client ⬢ May require a fully reactive backend ⬢ No open implementations yet
  61. GraphQL Subscriptions ⬢ Clients can subscribe to a specific type

    of event as a similar way as how we do mutations ⬢ Mutations are client-made changes while Subscriptions are server-pushed updates ⬢ New query results will be pushed to the client when a event occurred subscription { todoItemAddedToList(todoListID: "…") { todoItem { name } } }
  62. GraphQL Subscriptions ⬢ Clients can subscribe to a specific type

    of event as a similar way as how we do mutations ⬢ Mutations are client-made changes while Subscriptions are server-pushed updates ⬢ New query results will be pushed to the client when a event occurred ⬢ GraphQL just tells us how things should work, we need to configure different implementations (WebSocket, APNS, GCM) of sending the data on different platforms
  63. GraphQL Subscriptions UI $ ㄎ ㄎ ㄎ ㄎ d d

    d d d d Subscription Server Updater Store When event occurred On mount (normally)
  64. References ⬢ GraphQL API Explorer ⬢ GraphQL Concepts Visualized ⬢

    Mutations and Optimistic UI in Apollo Client ⬢ GraphQL Subscriptions in Apollo Client ⬢ https://github.com/zetavg/graphql-todomvc