$30 off During Our Annual Pro Sale. View Details »

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

    View Slide

  2. View Slide

  3. {
    me {
    name
    }
    }

    View Slide

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

    View Slide

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

    View Slide

  6. {
    "me": {
    "name": "Daniel Schafer",
    "profilePicture": {
    "width": 50,
    "height": 50,
    "url": "https://cdn/50.jpg"
    }
    }
    }
    {
    me {
    name
    profilePicture {
    width
    height
    url
    }
    }
    }

    View Slide

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

    View Slide

  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"
    }
    ]
    }
    }

    View Slide

  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"
    }
    ]
    }
    }

    View Slide

  10. graphql.org

    View Slide

  11. GraphQL: How?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. Facebook’s GraphQL Stack

    View Slide

  16. View Slide

  17. Think Graphs, not Endpoints

    View Slide

  18. Single Source of Truth

    View Slide

  19. Thin API layer

    View Slide

  20. Authorization

    View Slide

  21. View Slide

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

    View Slide

  23. What is a Todo Item?

    View Slide

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

    View Slide

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

    View Slide

  26. SELECT * FROM todoitems
    WHERE id = 4

    View Slide

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

    View Slide

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

    View Slide

  29. External Interfaces
    Storage Layer

    View Slide

  30. External Interfaces
    Business Logic
    Storage Layer

    View Slide

  31. External Interfaces
    Business Logic
    Storage Layer

    View Slide

  32. GraphQL
    Business Logic
    Storage Layer

    View Slide

  33. What is a Todo Item?

    View Slide

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

    View Slide

  35. http://sujaytrivedi.blogspot.com/2015/03/object-oriented-programming-oop-in-c.html

    View Slide

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

    View Slide

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

    View Slide

  38. Single Source of Truth

    View Slide

  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 {
    const data = await Redis.get("ti:" + id); // Nullable
    return data ? new TodoItem(data) : null;
    }
    }

    View Slide

  40. Implementing Authorization

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  44. class TodoItem {
    // Single source of truth for fetching
    static async gen(id: number): Promise {
    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
    }

    View Slide

  45. class TodoItem {
    // Single source of truth for fetching
    static async gen(id: number): Promise {
    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 */);
    }

    View Slide

  46. class TodoItem {
    // Single source of truth for fetching
    static async gen(id: number): Promise {
    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 */);
    }

    View Slide

  47. class TodoItem {
    // Single source of truth for fetching
    static async gen(
    viewer: Viewer, id: number): Promise {
    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 */);
    }

    View Slide

  48. class TodoItem {
    // Single source of truth for fetching
    static async gen(
    viewer: Viewer, id: number): Promise {
    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);
    }

    View Slide

  49. class TodoItem {
    static async gen(
    viewer: Viewer, id: number): Promise;
    // No other public constructors
    }
    class TodoList {
    static async gen(
    viewer: Viewer, id: number): Promise;
    // No other public constructors
    }

    View Slide

  50. Authorization in GraphQL

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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);

    View Slide

  58. Efficiency

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  70. Implementing Batching
    in GraphQL

    View Slide

  71. Implementing Batching
    in GraphQL

    View Slide

  72. DataLoader

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  80. Caching

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  85. Clients?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  89. {
    me {
    id
    profilePhoto {
    id
    }
    }
    }

    View Slide

  90. {
    me {
    id
    profilePhoto {
    id
    }
    }
    }
    {
    me: {
    id: "1",
    profilePhoto: {
    id: "1"
    }
    }
    }

    View Slide

  91. What do we want in IDs?

    View Slide

  92. Unique Cache Key

    View Slide

  93. Globally Unique Cache Key

    View Slide

  94. Globally Unique Cache Key
    Refetch Identifier

    View Slide

  95. Globally Unique Cache Key
    Refetch Identifier
    Client Comprehension?

    View Slide

  96. Globally Unique Cache Key
    Refetch Identifier
    Client Comprehension?

    View Slide

  97. Globally Unique Cache Key
    Refetch Identifier
    Opaque to Clients

    View Slide

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

    View Slide

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

    View Slide

  100. {
    me {
    id
    profilePhoto {
    id
    }
    }
    }

    View Slide

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

    View Slide

  102. Facebook’s GraphQL Stack

    View Slide

  103. Think Graphs, not Endpoints

    View Slide

  104. Single Source of Truth

    View Slide

  105. Thin API layer

    View Slide

  106. graphql.org

    View Slide