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

Scaling Your GraphQL Client

Scaling Your GraphQL Client

GraphQL's JSON responses are great. At least until you try to get a field that doesn't exist. Our client GraphQL interfaces can lead to surprising scaling issues. Learn from what Facebook has learned the hard way. See how client model choices can prevent bugs and help your team scale.

Matt Mahoney

June 20, 2019
Tweet

Other Decks in Programming

Transcript

  1. { "picture": { "url": "https://some-cdn.de/1234" }, "about": "I am clearly

    not a designer" } Response I am clearly not a designer
  2. Fragment Scale: one person fragment UserProfile on User { picture(width:

    40) { url } about } I am clearly not a designer
  3. Fragments Scale: one team fragment UserProfile on User { ...SquarePic

    about } fragment SquarePic on HasPic { picture(style: SQUARE) { url } } I am clearly not a designer
  4. I am clearly not a designer All About Canada Goose!

    Fragments Scale: many teams fragment UserProfile on User { ...SquarePic about } fragment GroupProfile on Group { ...SquarePic info } fragment SquarePic on HasPic { picture(style: SQUARE) { ...CoreImage } } fragment CoreImage on Image { url }
  5. Fragments Scale: many apps fragment AppPic on HasPic { ...SquarePic

    } fragment AppPic on HasPic { ...RoundPic } I am clearly not a designer All About Canada Goose! I am clearly not a designer All About Canada Goose! fragment UserProfile on User { ...AppPic } fragment GroupProfile on Group { ...AppPic }
  6. Scaling Your GraphQL Client Matt Mahoney • JSON Models •

    Type Models • Response Models • Fragment Models
  7. JSON Models Model fragment UserProfile on User { picture {

    url width } } type User { about: String picture(width: Int): Image! } type Image { url: URL! } interface JSONModel { @Nullable String getString(String key); @Nullable Int getInt(String key); @Nullable JSONModel getJSON(String key); }
  8. JSON Models Scale: One Person fragment UserProfile on User {

    picture { url width } } I am clearly not a designer
  9. Raw JSON Usage class UserProfileComponent { Component render(JSONModel model) {

    JSONModel pic = model.getJSON("picture"); String uri = pic.getString("uri"); int about = model.getInt("about"); ... } } I am clearly not a designer
  10. Correctness fragment UserProfile on User { picture { url width

    } } class UserProfileComponent { Component render(JSONModel model) { JSONModel pic = model.getJSON("picture"); String uri = pic.getString("uri"); int about = model.getInt("about"); ... } } • Typos • No type safety • Underfetch • Overfetch JSON Models
  11. • One person • Owns Schema and Client • Well-defined,

    stable types • Very small queries • Can test every change every release JSON Models Recommended Scale
  12. Type Models Model type User implements HasPic { about: String

    picture(width: Int): Image! } type Image { url: URL! width: Int! } interface UserModel extends HasPicModel { @Nullable String getAbout(); ImageModel getPicture(); } interface ImageModel { URL getUrl(); int getWidth(); }
  13. Type Models Issue: aliases Component render(HasPicModel model) { ImageModel lilPic

    = model.getPicture(); ... } fragment UserProfile on User { ...SquarePic about } fragment SquarePic on HasPic { lilPic: picture(width: 40) { url } bigPic: picture(width: 400) { url } } I am clearly not a designer
  14. Type Models Issue: scaling interface UserModel { String getName(); @Nullable

    String getAbout(); ImageModel getPicture(); ... } interface ImageModel { URL getUrl(); ... } ... • Class per type • Facebook: ~30,000 types • Getter per field • Facebook: ~200,000 fields
  15. interface UserModel extends HasPicModel { @Nullable String getAbout(); ImageModel getLilPic();

    ImageModel getBigPic(); } interface ImageModel { URL getUrl(); int getWidth(); } Type Models Model (queried fields) fragment UserProfile on User { about } fragment SquarePic on HasPic { lilPic: picture(width: 40) { url @include(if: $b) } }
  16. Type Models Usage Component renderUserProfile(UserModel model) { String about =

    model.getAbout(); renderUserImage(model); } Component renderSquarePic(HasPicModel model) { Image image = model.getLilPic(); URL url = model.getUrl(); } I am clearly not a designer
  17. Type Models Correctness • Typos • No type safety •

    Underfetch • Overfetch Component renderUserProfile(UserModel model) { String about = model.getAbout(); renderSquarePic(model); } Component renderSquarePic(HasPicModel model) { Image image = model.getLilPic(); URL url = model.getUrl(); }
  18. Type Models Correctness • Typos • Some type safety •

    Underfetch • Overfetch Component renderUserProfile(UserModel model) { String about = model.getAbout(); renderSquarePic(model); } Component renderSquarePic(HasPicModel model) { Image image = model.getLilPic(); URL url = model.getUrl(); }
  19. I am clearly not a designer fragment SquarePic on HasPic

    { lilPic: picture(width: 40) { url @include(if: $b) } } Correctness: Type Safety Type Models interface ImageModel { URL getUrl(); Int getWidth(); }
  20. Component renderUserProfile(UserModel model) { renderUserImage(model); } Component renderSquarePic(HasPicModel model) {

    Image image = model.getLilPic(); URL url = model.getUrl(); } fragment UserProfile on User { about } Correctness: Underfetch Type Models fragment SquarePic on HasPic { lilPic: picture(width: 40) { url @include(if: $b) } }
  21. Correctness: Overfetch Type Models fragment SquarePic on User { lilPic:

    picture(width: 40) { ...CoreImage } bigPic: picture(width: 40) { ...CoreImage } } Component renderSquarePic(HasPicModel model) { renderImage(model.getLilPic()); } I am clearly not a designer
  22. • Many teams: • All teams share one gigantic type

    model library • Or no re-usable components across teams • Or split into "shared universes": • Choose correct universe or refactor! • Builds break with other teams' deletions • Additions can cause breakages too Type Models Scaling: No Modularity
  23. • Tens of people • Model size scales with app

    usage • Requires a lot of testing to keep correct • Library grows to O(schema) Type Models Recommended Scale
  24. Response Models Model fragment UserProfile on User { ...SquarePic }

    fragment SquarePic on HasPic { lilPic: picture(width: 40) { ...CoreImage } } fragment CoreImage on Image { url @include(if: $b) } interface UserProfile extends SquarePic { } interface SquarePic { SquarePic.LilPic getLilPic(); interface LilPic extends CoreImage { } } interface CoreImage { @Nullable URL getUrl(); }
  25. Response Models Scale: Many Teams I am clearly not a

    designer All About Canada Goose!
  26. Response Models Usage Component renderProfile(UserProfile model) { URL url =

    model.getLilPic().getUrl(); renderUserImage(model); } Component renderSquarePic(SquarePic model) { UserImage.LilPic lilPic = model.getLilPic(); renderCoreImage(lilPic); } interface UserProfile extends SquarePic {} interface SquarePic { SquarePic.LilPic getLilPic(); interface LilPic extends CoreImage {} } interface CoreImage { @Nullable URL getUrl(); }
  27. Response Models Correctness • Typos • Some type safety •

    Underfetch • Overfetch Component renderProfile(UserProfile model) { URL url = model.getLilPic().getUrl(); renderSquarePic(model); } Component renderSquarePic(SquarePic model) { LilPic lilPic = model.getLilPic(); Component image = renderCoreImage(lilPic); }
  28. Component renderSquarePic(SquarePic model) { LilPic lilPic = model.getLilPic(); URL url

    = lilPic.getUrl(); Component image = renderCoreImage(lilPic); } fragment SquarePic on HasPic { lilPic: picture(width: 40) { ...CoreImage } bigPic: picture(width: 400) { ...CoreImage } } Response Models Correctness: Overfetch
  29. fragment CoreImage on Image { url @include(if: $b) } Component

    renderSquarePic(SquarePic model) { LilPic lilPic = model.getLilPic(); URI uri = lilPic.getUrl(); Component image = renderCoreImage(lilPic); } Response Models Scaling: Encapsulation interface CoreImage { URL getUrl(); } • Remove field • Must fix all parent usages • Recursively • Hour-long build times at FB • Other app builds break!
  30. Response Models Scaling: Many Apps • Contextual fragments • Which

    interface to inherit? • Back to type models? • FB choice: • Infra: type models • Product: Response models • Convert as needed fragment UserProfile on User { ...AppPic } # Circles App fragment AppPic on HasPic { circlePic: picture(style: CIRCLE) { ...CoreImage } } # Squares App fragment AppPic on HasPic { squarePic: picture(style: SQUARE) { ...CoreImage } }
  31. • Many teams • Encapsulation problems grow with product •

    Impossible to inject app-specific fragments • Inheritance chains grow to O(queries) Response Models Recommended Scale
  32. Model Fragment Models // Shared across apps interface UserProfile {

    AppPic asAppPic(); } interface CoreImage { ... } // Squares App model interface AppPic { SquarePic getSquarePic(); interface SquarePic { @Nullable CoreImage asCoreImage(); } } fragment UserProfile on User { ...AppPic } # Squares App fragment fragment AppPic on HasPicture { squarePic: picture(style: SQUARE) { ...CoreImage @include(if: $b) } }
  33. Fragment Models Scale: Many Apps I am clearly not a

    designer All About Canada Goose! I am clearly not a designer All About Canada Goose!
  34. Usage Fragment Models interface UserProfile { AppSpecificUser asAppPic(); } //

    @Injected Squares App codegen interface AppPic { SquarePic getSquarePic(); interface SquarePic { @Nullable CoreImage asCoreImage(); } } class UserProfileComponent { Component render(UserProfile model) { renderPic(model.asAppPic()); } } // @Injected class AppPic { Component renderPic(AppPic model) { SquarePic pic = model.getSquarePic(); if (pic.asCoreImage()) { renderCoreImage(pic.asCoreImage()); } } }
  35. Correctness Fragment Models • Typos • Some type safety •

    Underfetch • Overfetch class UserProfileComponent { Component render(UserProfile model) { renderPic(model.asAppPic()); } } // @Injected class AppPictureComponent { Component renderPic(AppPic model) { SquarePic pic = model.getSquarePic(); if (pic.asCoreImage()) { renderCoreImage(pic.asCoreImage()); } } }
  36. Scaling: Encapsulation Fragment Models • Cannot access child fields •

    Fragments become black boxes • Build times: O(queries) => O(fragment) • Reduces over-fetch: safe, easy field deletion
  37. Fragment Models Scaling: Many Apps • Each app has own

    design team • Engineers want to re-use components • Models are black boxes • Inject app-specific component matching design
  38. • Many teams across many apps • Facebook has not

    hit scaling limit yet • Relay uses Fragment Models • Native Objective-C/Java moving to Fragment Models Fragment Models Recommended Scale