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. { "me": { "name": "Daniel Schafer", "profilePicture": { "width": 50,

    "height": 50, "url": "https://cdn/50.jpg" } } } { me { name profilePicture { width height url } } }
  2. 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" } ] } }
  3. 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" } ] } }
  4. How do I implement authorization? How do I make GraphQL

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

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

    efficient? How do I cache my results?
  7. class TodoItem { constructor(id: number, title: string, isDone: boolean) {

    this.id = id; this.title = title; this.isDone = isDone; } };
  8. class TodoItem { constructor(id: number, title: string, isDone: boolean) {

    this.id = id; this.title = title; this.isDone = isDone; } };
  9. 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; } }
  10. 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; } }
  11. 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; } }
  12. 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; } }
  13. 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 }
  14. 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 */); }
  15. 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 */); }
  16. 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 */); }
  17. 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); }
  18. 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 }
  19. todoItem: { type: TodoItemType, resolve: (obj) => /* ??? */

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

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

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

    } }, resolve: (obj, {id}, viewer) => TodoItem.gen(viewer, id) } // ... graphql(schema, "{todoItem(id: $id)}", viewer);
  23. 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);
  24. { me { name bestFriend { name } friends(first: 5)

    { name bestFriend { name } } } }
  25. { 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
  26. { 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
  27. { 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
  28. { 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
  29. { 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
  30. { 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
  31. { 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
  32. { 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
  33. { 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
  34. { 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
  35. 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; } }
  36. 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; } }
  37. 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; } }
  38. 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; } }
  39. { 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
  40. { 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
  41. 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; } }
  42. 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; } }
  43. { 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
  44. { 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
  45. 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; } }
  46. 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; } }
  47. const userType = new GraphQLObjectType({ name: 'User', fields: () =>

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

    { me: { id: "1", profilePhoto: { id: "1" } } }
  49. const userType = new GraphQLObjectType({ name: 'User', fields: () =>

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

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

    { me: { id: "dXNlcjoxCg==", profilePhoto: { id: "cGhvdG86MQo=" } } }