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

GraphQL: Client-Driven Development

GraphQL: Client-Driven Development

GraphQL Europe 2017

Dan Schafer

May 21, 2017
Tweet

More Decks by Dan Schafer

Other Decks in Programming

Transcript

  1. GraphQL: Client-Driven Development Dan Schafer Software Engineer, Facebook @dlschafer

  2. https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html

  3. Product-centric: GraphQL is unapologetically driven by the requirements of views

    and the front-end engineers that write them. We start with their way of thinking and requirements and build the language and runtime necessary to enable that. https://facebook.github.io/react/blog/2015/05/01/graphql-introduction.html
  4. History of GraphQL July ’15 Aug ’12 Evolution Open Source

    Feb ’12 May ’17 Prototype
  5. History of GraphQL Aug ’12 Prototype Feb ’12

  6. Think Graphs, not Endpoints Single Source of Truth Thin API

    layer
  7. Jan ’12 Prototype iOS Client {stories: {…}} SELECT * FROM

    stories Server
  8. Jan ’12 Prototype iOS Client {stories: {…}} SELECT * FROM

    stories Server SELECT stories.id, stories.title, users.name, COUNT(likers.id) FROM stories LEFT JOIN users ON users.id = stories.author_id LEFT JOIN likers ON likers.post_id = stories.id LIMIT 10
  9. Jan ’12 Prototype iOS Client {stories: {…}} SELECT * FROM

    stories Server
  10. Jan ’12 Prototype iOS Client FQL Business Logic
 +
 Storage

    Layer {stories: {…}} SELECT * FROM stories
  11. Feb ’12 Prototype iOS Client Business Logic
 +
 Storage Layer

    {stories: {…}} stories.fields(title)
  12. Feb ’12 Prototype iOS Client GQL Business Logic
 +
 Storage

    Layer {stories: {…}} stories.fields(title)
  13. Think Graphs, not Endpoints Single Source of Truth Thin API

    layer
  14. History of GraphQL July ’15 Aug ’12 Evolution

  15. 1. SCALING MODELS 2. SCALING VIEWS 3. SCALING THE APP

  16. query { me { name profilePicture { width height url

    } } }
  17. nickname profilePicture { width height url } } } query

    { me { name
  18. None
  19. None
  20. None
  21. <?xml version='1.0' encoding='utf-8'?> <model documentVersion="1.0"> <entity name="User" representedClassName="FBUser" syncable="YES"> <attribute

    attributeType="String" name="name" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <userInfo> <entry key="fb.graphQLType" value="User" /> </userInfo> </entity> <elements> <element height="255" name="User" positionX="160" positionY="192" width="128" /> </elements> </model> CoreData XML Files
  22. None
  23. None
  24. Generated Code

  25. Codegen

  26. query { me { name profilePicture { height width uri

    } } } Codegen
  27. query { me { name profilePicture { height width uri

    } } } <?xml version='1.0' encoding='utf-8'?> <model documentVersion="1.0"> <entity name="User" representedClassName="FBUser" syncable="YES"> <attribute attributeType="Boolean" name="isOwned" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <userInfo> <entry key="fb.graphQLType" value="User" /> </userInfo> </entity> <elements> <element height="255" name="User" positionX="160" positionY="192" width="128" /> </elements> </model> Codegen
  28. Static Queries (a.k.a. .graphql files)

  29. FBGraphQLNode *user = [self graphQLNode]; [user addField:FBUserFields.name expectedType:kFBGraphQLTypeString]; return user;

    Code Reuse
  30. FBGraphQLNode *user = [self graphQLNode]; [user addField:FBUserFields.name expectedType:kFBGraphQLTypeString]; FBAttachProfilePictureGraphQLSubnode(user); return

    user; Code Reuse
  31. query { me { name profilePicture { height width uri

    } } }
  32. query { me { name ...ProfilePicture } }

  33. Codegen → Fragments for Code Reuse

  34. query { me { name ...ProfilePicture } } fragment ProfilePicture

    on User { profilePicture { height width uri } }
  35. query { me { name profilePicture { height width uri

    } } }
  36. None
  37. None
  38. Client-Driven Development

  39. 1. SCALING MODELS 2. SCALING VIEWS 3. SCALING THE APP

  40. <?xml version='1.0' encoding='utf-8'?> <model documentVersion="1.0"> <entity name="User" representedClassName="FBUser" syncable="YES"> <attribute

    attributeType="String" name="name" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <userInfo> <entry key="fb.graphQLType" value="User" /> </userInfo> </entity> <elements> <element height="255" name="User" positionX="160" positionY="192" width="128" /> </elements> </model> CoreData XML Files
  41. <?xml version='1.0' encoding='utf-8'?> <model documentVersion="1.0"> <entity name="User" representedClassName="FBUser" syncable="YES"> <attribute

    attributeType="String" name="name" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="String" name="Amet" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Lorem" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Ipsum" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Dolor" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Sit" optional="YES" syncable="YES" /> <attribute attributeType="Boolean" name="Amet" optional="YES" syncable="YES" /> <userInfo> <entry key="fb.graphQLType" value="User" /> </userInfo> </entity> <elements> <element height="255" name="User" positionX="160" positionY="192" width="128" /> </elements> </model> CoreData XML Files
  42. React

  43. None
  44. Litho

  45. Flux

  46. Flux Dispatcher Action Store View Action

  47. GraphQL Clients circa 2013 Dispatcher Action Store View Action GraphQL


    Queries
  48. GraphQL Clients circa 2013 Dispatcher Action Store View Action GraphQL


    Queries GraphQL
 Engine
  49. GraphQL Clients circa 2013 Dispatcher Action Store View Action GraphQL


    Queries GraphQL
 Engine Immutable
 Model
 Store
  50. GraphQL Clients circa 2013 Dispatcher Action Store View Action GraphQL


    Queries GraphQL
 Engine Immutable
 Model
 Store Reactive
 Views
  51. GraphQL Clients circa 2013 GraphQL
 Engine GraphQL
 Queries Immutable
 Model


    Store Reactive
 Views GraphQL
 Mutations
  52. Flux → GraphQL Mutations

  53. “Why top level mutations?”

  54. “Why actions and not CRUD?”

  55. GraphQL Clients circa 2013 GraphQL
 Engine GraphQL
 Queries Immutable
 Model


    Store Reactive
 Views GraphQL
 Mutations
  56. Mutations

  57. Mutations?

  58. Client-Driven Development

  59. true POST /1/like/ {page:{likers:{count:1}}} {page(id:1){likers{count}}}

  60. true POST /1/like/ {page:{likers:{count:1}}} {page(id:1){likers{count}}} Batch API

  61. REST Response REST POST Query Response GraphQL Query Batch API

    Client Shim GraphQL Mutation Mutation Response
  62. Incrementally Add Value

  63. { "page_like": { "graph_endpoint": { "method": "POST", "url": { "format":

    "%@/likes", "variables": ["${page_id}"] }, "params": { "analytics": "JSON(${analytics})" } }, "input": { "page_id": "String!", "analytics": "[String!]" }, "payload": { "page": { "type": "Page", "query": "{node($id){...Contents}}", "query_variables": { "id": "${page_id}" } } } } } mutations.json
  64. mutation PageLikeMutation( $page_id: String!, $analytics: [String!] ) { page_like(page_id: $page_id,

    analytics: $analytics) { page { id likers { count } } } }
  65. + (FBNetworkerRequest *)requestWithInput:(FBPageLikeInputData *)input withGraphQLQuery:(FBGraphQLRequest *)query { NSString *path =

    [NSString stringWithFormat:@"%@/likes", input.pageId]; FBGraphRequest *mutationRequest = [[FBGraphRequest alloc] initWithPath:path]; FBGraphBatchRequest *batchRequest = [[FBGraphBatchRequest alloc] initWithRequest:mutationRequest]; FBGraphQLRequest *queryRequest = [[FBGraphQLRequest alloc] initWithQuery:query]; [batchRequest addGraphRequest:queryRequest]; return batchRequest; } Auto-Generated Code Auto-Generated Code
  66. None
  67. None
  68. 1. SCALING MODELS 2. SCALING VIEWS 3. SCALING THE APP

  69. None
  70. Declarative!

  71. Declarative…ish

  72. None
  73. None
  74. https://wincent.com/blog/relay-modern

  75. One of the big ideas was query colocation — the

    notion that you should be able to specify your data requirements for each view component inside the view itself and that the framework should transparently handle aggregation and efficient fetching. https://wincent.com/blog/relay-modern
  76. Colocation!

  77. Fragments for Code Reuse

  78. Fragments as a Primitive

  79. None
  80. type User { name: String! profilePicture(size: Int = 50): ProfilePicture

    friends(first: Int): [User!]! events(first: Int): [Event!]! }
  81. type User { name: String! profilePicture(size: Int = 50): ProfilePicture

    friends(first: Int): [User!]! events(first: Int): [Event!]! } type User = {| name: string, profilePicture: ?ProfilePicture, friends: Array<User>, events: Array<Event>, |};
  82. type User = {| name: string, profilePicture: ?ProfilePicture, friends: Array<User>,

    events: Array<Event>, |}; type User { name: String! profilePicture(size: Int = 50): ProfilePicture friends(first: Int): [User!]! events(first: Int): [Event!]! }
  83. query { me { profilePicture { uri } } }

  84. type User = {| name: string, profilePicture: ?ProfilePicture, friends: Array<User>,

    events: Array<Event>, |}; type User { name: String! profilePicture(size: Int = 50): ProfilePicture friends(first: Int): [User!]! events(first: Int): [Event!]! }
  85. type User = {| name: ?string, profilePicture: ?ProfilePicture, friends: ?Array<?User>,

    events: ?Array<?Event>, |}; type User { name: String! profilePicture(size: Int = 50): ProfilePicture friends(first: Int): [User!]! events(first: Int): [Event!]! }
  86. query Timeline { me { displayName: name } } query

    Bookmarks { me { displayName: nickname } }
  87. type User = {| displayName: ?string |}; query Timeline {

    me { displayName: name } } query Bookmarks { me { displayName: nickname } }
  88. query ProfilePic { me { pic: profilePicUri } } query

    CoverPhoto { me { pic: coverPhoto { uri } } }
  89. type User = {| pic: any |}; query ProfilePic {

    me { pic: profilePicUri } } query CoverPhoto { me { pic: coverPhoto { uri } } }
  90. Type Models

  91. Type Models

  92. Fragments as a Primitive

  93. fragment UserWithPic on User { profilePicture { uri } }

  94. type UserWithPic = {| profilePicture: ?ProfilePicture |}; type ProfilePicture =

    {| uri: string |}; fragment UserWithPic on User { profilePicture { uri } }
  95. Fragment Models

  96. History of GraphQL July ’15 Open Source May ’17

  97. Future of GraphQL Open Source

  98. query { me { name nickname } }

  99. query { me { name nickname # Only if in

    nickname test group } }
  100. query { me { name nickname(if: $in_nickname_test) } }

  101. query { me { name nickname @include(if: $in_nickname_test) } }

  102. @future

  103. Client-Driven Development

  104. GraphQL: Client-Driven Development Dan Schafer Software Engineer, Facebook @dlschafer