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

Twitch's GraphQL Transformation

Tony Ghita
October 26, 2017

Twitch's GraphQL Transformation

Twitch has over six hundred engineers, millions of concurrent viewers, and is one of the highest trafficked sites on the internet today. Our API handles hundreds of thousands of requests per second and powers many different clients. The API aggregates hundreds of underlying services together in a singular, (hopefully) coherent package.

Adopting a GraphQL API enabled us to rewrite our aging web and mobile clients within a few months, giving users a much snappier experience. We’ve built tooling to optimize for developer velocity on the service side as well. Code generation keeps the cost of adding types and fields minimal, and automated integration tests ensure changes are safe. We’ve wrangled dataloaders into shape to provide efficient, high-performance operations across a multitude of backing services.

And perhaps most importantly, GraphQL has acted as a guiding force to a more standardized and flexible service ecosystem. We’ve been pushed to reconsider past decisions around service aspects like authorization and pagination, and have come out with much improved systems.

Presented at GraphQL Summit 2017.

Tony Ghita

October 26, 2017
Tweet

More Decks by Tony Ghita

Other Decks in Technology

Transcript

  1. sum m it 2016 sum m it 2017 proof-of-concept type

    com position & pagination authN / authZ ??? production beta PROTOTYPE PRODUCTION
  2. sum m it 2017 proof-of-concept type com position & pagination

    authN / authZ ??? live beta PROTOTYPE PRODUCTION sum m it 2016
  3. sum m it 2016 sum m it 2017 type com

    position & pagination authN / authZ ??? production beta PROTOTYPE PRODUCTION proof-of-concept
  4. – Cruise Industry News Quarterly, 1999 “... if Henry Ford

    canvassed people on whether or not he should build a motor car, they'd probably tell him what they really wanted was a faster horse”
  5. sum m it 2016 sum m it 2017 proof-of-concept authN

    / authZ ??? production beta PROTOTYPE PRODUCTION type com position & pagination
  6. type Query { users(ids: [ID!], names: [String!]): [User] streams(first: Int

    = 10): [Stream] games(first: Int = 10): [Game] } type User { id: ID name: String } type Stream { id: ID broadcaster: User game: Game name: String viewers: Int } type Game { id: ID name: String viewers: Int }
  7. type Query { games(first: Int, after: Cursor): GameConnection } query

    getFeaturedGames { games(first: 10, after: "a01s4 ==") { edges { cursor
 node { id, name, viewersCount } } } }
  8. sum m it 2016 sum m it 2017 proof-of-concept type

    com position & pagination authN / authZ ??? production beta PROTOTYPE PRODUCTION
  9. :\

  10. ;)

  11. sum m it 2016 sum m it 2017 proof-of-concept type

    com position & pagination authN / authZ production beta PROTOTYPE PRODUCTION ???
  12. type User { id: ID! name: String! } func (r

    *UserResolver) ID() (graphql.ID, error) { user, err := r.loadFn() if err != nil { return graphql.ID(""), err } if user == nil { return graphql.ID(""), errors.New("not found") } return graphql.ID(user.ID), nil } func (r *UserResolver) Name() (string, error) { user, err := r.loadFn() if err != nil { return "", err } if user == nil { return "", errors.New("not found") } return user.Name, nil } type UserResolver struct { loadFn *dataloader.Thunk }
  13. type User { id: ID! name: String! } func (r

    *UserResolver) ID() (graphql.ID, error) { user, err := r.loadFn() if err != nil { return graphql.ID(""), err } if user == nil { return graphql.ID(""), errors.New("not found") } return graphql.ID(user.ID), nil } func (r *UserResolver) Name() (string, error) { user, err := r.loadFn() if err != nil { return "", err } if user == nil { return "", errors.New("not found") } return user.Name, nil } type UserResolver struct { loadFn *dataloader.Thunk }
  14. type User { id: ID! name: String! } func (r

    *UserResolver) ID() (graphql.ID, error) { user, err := r.loadFn() if err != nil { return graphql.ID(""), err } if user == nil { return graphql.ID(""), errors.New("not found") } return graphql.ID(user.ID), nil } func (r *UserResolver) Name() (string, error) { user, err := r.loadFn() if err != nil { return "", err } if user == nil { return "", errors.New("not found") } return user.Name, nil } type UserResolver struct { loadFn *dataloader.Thunk }
  15. type User { id: ID! name: String! } func (r

    *UserResolver) ID() (graphql.ID, error) { user, err := r.loadFn() if err != nil { return graphql.ID(""), err } if user == nil { return graphql.ID(""), errors.New("not found") } return graphql.ID(user.ID), nil } func (r *UserResolver) Name() (string, error) { user, err := r.loadFn() if err != nil { return "", err } if user == nil { return "", errors.New("not found") } return user.Name, nil } type UserResolver struct { loadFn *dataloader.Thunk }
  16. – African Proverb “If you want to go fast, go

    alone. If you want to go far, go together.”
  17. sum m it 2016 sum m it 2017 proof-of-concept type

    com position & pagination authN / authZ PROTOTYPE PRODUCTION ??? production beta
  18. query getNullabilityFail { user(name: "does not exist") { followers {

    nodes { id, name } } } } type Query { user(name: String): User }
  19. query getNullabilityFail { user(name: "does not exist") { followers {

    nodes { id, name } } } } { "data": { "user": { "followers": { "nodes": [] }
 } } }
  20. query getNullabilityFail { user(name: "does not exist") { followers {

    nodes { id, name } } } } { "data": { "user": { "followers": { "nodes": [] }
 } } } X
  21. query getNullabilityFail { user(name: "does not exist") { followers {

    nodes { id, name } } } } { "data": { "user": null } }