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

GraphQL at Facebook

GraphQL at Facebook

GraphQL at Facebook
Dan Schafer
ReactEurope, 2 June 2016
https://www.react-europe.org/

Dan Schafer

June 02, 2016
Tweet

More Decks by Dan Schafer

Other Decks in Programming

Transcript

  1. Facebook’s GraphQL Stack Dan Schafer

  2. None
  3. { me { name } }

  4. { "me": { "name": "Daniel Schafer" } } { me

    { name } }
  5. { me { name profilePicture { width height url }

    } }
  6. { "me": { "name": "Daniel Schafer", "profilePicture": { "width": 50,

    "height": 50, "url": "https://cdn/50.jpg" } } } { me { name profilePicture { width height url } } }
  7. query Q { me { name birthday teammates { name

    birthday } } }
  8. query Q { me { name birthday teammates { name

    birthday } } } { "me": { "name": "Daniel Schafer", "birthday": "Jan 17", "teammates": [ { "name": "Lee Byron", "birthday": "Dec 18" }, { "name": "Laney Kuenzel", "birthday": "Jan 25" } ] } }
  9. query Q { me { ...BasicInfo teammates { ...BasicInfo }

    } } fragment BasicInfo on User { name birthday } { "me": { "name": "Daniel Schafer", "birthday": "Jan 17", "teammates": [ { "name": "Lee Byron", "birthday": "Dec 18" }, { "name": "Laney Kuenzel", "birthday": "Jan 25" } ] } }
  10. graphql.org

  11. GraphQL: How?

  12. How do I implement authorization? How do I make GraphQL

    efficient? How do I cache my results?
  13. How do I implement authorization? How do I make GraphQL

    efficient? How do I cache my results?
  14. How do I implement authorization? How do I make GraphQL

    efficient? How do I cache my results?
  15. Facebook’s GraphQL Stack

  16. None
  17. Think Graphs, not Endpoints

  18. Single Source of Truth

  19. Thin API layer

  20. Authorization

  21. None
  22. “A Todo Item can only be seen by its creator.”

  23. What is a Todo Item?

  24. http://api.todoapp.com/todo/4

  25. http://api.todoapp.com/todo/4 Too interface-specific

  26. SELECT * FROM todoitems WHERE id = 4

  27. SELECT * FROM todoitems WHERE id = 4 GET todoitem:4

  28. SELECT * FROM todoitems WHERE id = 4 Storage Detail

    GET todoitem:4
  29. External Interfaces Storage Layer

  30. External Interfaces Business Logic Storage Layer

  31. External Interfaces Business Logic Storage Layer

  32. GraphQL Business Logic Storage Layer

  33. What is a Todo Item?

  34. class TodoItem { constructor(id: number, title: string, isDone: boolean) {

    this.id = id; this.title = title; this.isDone = isDone; } };
  35. http://sujaytrivedi.blogspot.com/2015/03/object-oriented-programming-oop-in-c.html

  36. https://upload.wikimedia.org/wikipedia/commons/b/b8/Policy_Admin_Component_Diagram.PNG

  37. class TodoItem { constructor(id: number, title: string, isDone: boolean) {

    this.id = id; this.title = title; this.isDone = isDone; } };
  38. Single Source of Truth

  39. class TodoItem { constructor(data: TodoItemData) { this.id = data.id; this.title

    = data.title; this.isDone = data.isDone; } // Single source of truth for fetching static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable return data ? new TodoItem(data) : null; } }
  40. Implementing Authorization

  41. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable return data ? new TodoItem(data) : null; } }
  42. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable // Single source of truth for authorization return data ? new TodoItem(data) : null; } }
  43. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(data); return canSee ? new TodoItem(data) : null; } }
  44. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(data); return canSee ? new TodoItem(data) : null; } } function checkCanSee(data: Object) { // A Todo Item can only be seen by its creator }
  45. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(data); return canSee ? new TodoItem(data) : null; } } function checkCanSee(data: Object) { return (data.creatorID === /* authenticated user ID */); }
  46. class TodoItem { // Single source of truth for fetching

    static async gen(id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(data); return canSee ? new TodoItem(data) : null; } } function checkCanSee(data: Object) { return (data.creatorID === /* authenticated user ID */); }
  47. class TodoItem { // Single source of truth for fetching

    static async gen( viewer: Viewer, id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(data); return canSee ? new TodoItem(data) : null; } } function checkCanSee(data: Object) { return (data.creatorID === /* authenticated user ID */); }
  48. class TodoItem { // Single source of truth for fetching

    static async gen( viewer: Viewer, id: number): Promise<?TodoItem> { const data = await Redis.get("ti:" + id); // Nullable if (data === null) return null; const canSee = checkCanSee(viewer, data); return canSee ? new TodoItem(data) : null; } } function checkCanSee(viewer: Viewer, data: Object) { return (data.creatorID === viewer.userID); }
  49. class TodoItem { static async gen( viewer: Viewer, id: number):

    Promise<?TodoItem>; // No other public constructors } class TodoList { static async gen( viewer: Viewer, id: number): Promise<?TodoList>; // No other public constructors }
  50. Authorization in GraphQL

  51. todoItem: { type: TodoItemType, resolve: (obj) => /* ??? */

    }
  52. todoItem: { type: TodoItemType, resolve: (obj) => /* ??? */

    } // ... graphql(schema, "{todoItem}");
  53. todoItem: { type: TodoItemType, resolve: (obj) => TodoItem.gen(viewer, id) }

    // ... graphql(schema, "{todoItem}");
  54. todoItem: { type: TodoItemType, args: { id: { type: GraphQLID

    } }, resolve: (obj, {id}) => TodoItem.gen(viewer, id) } // ... graphql(schema, "{todoItem(id: 4)}");
  55. todoItem: { type: TodoItemType, args: { id: { type: GraphQLID

    } }, resolve: (obj, {id}, viewer) => TodoItem.gen(viewer, id) } // ... graphql(schema, "{todoItem(id: $id)}", viewer);
  56. todoItem: { type: TodoItemType, args: { id: { type: GraphQLID

    } }, resolve: (obj, {id}, viewer) => TodoItem.gen(viewer, id) } // ... graphql(schema, "{todoItem(id: $id)}", viewer);
  57. todoItem: { type: TodoItemType, args: { id: { type: GraphQLID

    } }, resolve: (obj, {id}, viewer) => TodoItem.gen(viewer, id) } // ... const viewer = Viewer.fromAuthToken(request.auth_token); graphql(schema, "{todoItem(id: $id)}", viewer);
  58. Efficiency

  59. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } }
  60. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  61. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  62. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  63. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  64. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:2 GET u:3 GET u:4 GET u:5 GET u:6 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  65. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:2 GET u:3 GET u:4 GET u:5 GET u:6 GET u:1 GET u:5 GET u:7 GET u:8 GET u:9 redis.log
  66. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 -1 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 GET u:8 GET u:9 GET u:10 GET u:11 GET u:12 redis.log
  67. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 GET u:3 GET u:4 GET u:5 GET u:6 GET u:7 redis.log
  68. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:8 u:9 u:10 u:11 u:12 redis.log
  69. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:1 u:5 u:7 u:8 u:9 redis.log
  70. Implementing Batching in GraphQL

  71. Implementing Batching in GraphQL

  72. DataLoader

  73. https://github.com/facebook/dataloader

  74. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "m:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await Redis.get("u:" + id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  75. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "m:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await Redis.get("u:" + id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  76. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "u:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await Redis.get("u:" + id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  77. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "u:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await userDataLoader.load(id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  78. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:1 u:5 u:7 u:8 u:9 redis.log
  79. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:1 u:5 u:7 u:8 u:9 redis.log
  80. Caching

  81. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "u:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await userDataLoader.load(id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  82. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "u:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await userDataLoader.load(id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } }
  83. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:1 u:5 u:7 u:8 u:9 redis.log
  84. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } } GET u:1 GET u:2 LRANGE friends:1 0 5 MGET u:2 u:3 u:4 u:5 u:6 MGET u:1 u:5 u:7 u:8 u:9 redis.log
  85. Clients?

  86. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "m:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await userDataLoader.load(id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } getID(): number { return this.id; } }
  87. const userDataLoader = new DataLoader( ids => Redis.mget(ids.map(id => "m:"

    + id)) ); class User { static async gen(v: Viewer, id: number): Promise<?User> { const rawData = await userDataLoader.load(id); // Do authorization ... return rawData === null ? new TodoItem(rawData) : null; } getID(): number { return this.id; } }
  88. const userType = new GraphQLObjectType({ name: 'User', fields: () =>

    ({ id: { type: GraphQLID, resolve: (user) => user.getID() } }) });
  89. { me { id profilePhoto { id } } }

  90. { me { id profilePhoto { id } } }

    { me: { id: "1", profilePhoto: { id: "1" } } }
  91. What do we want in IDs?

  92. Unique Cache Key

  93. Globally Unique Cache Key

  94. Globally Unique Cache Key Refetch Identifier

  95. Globally Unique Cache Key Refetch Identifier Client Comprehension?

  96. Globally Unique Cache Key Refetch Identifier Client Comprehension?

  97. Globally Unique Cache Key Refetch Identifier Opaque to Clients

  98. const userType = new GraphQLObjectType({ name: 'User', fields: () =>

    ({ id: { type: GraphQLID, resolve: (user) => user.getID() } }) });
  99. const userType = new GraphQLObjectType({ name: 'User', fields: () =>

    ({ id: { type: GraphQLID, resolve: (user) => base64("user:" + user.getID()) } }) });
  100. { me { id profilePhoto { id } } }

  101. { me { id profilePhoto { id } } }

    { me: { id: "dXNlcjoxCg==", profilePhoto: { id: "cGhvdG86MQo=" } } }
  102. Facebook’s GraphQL Stack

  103. Think Graphs, not Endpoints

  104. Single Source of Truth

  105. Thin API layer

  106. graphql.org