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

GraphQL, what is it good for?

GraphQL, what is it good for?

As presented at the Ruby a la Cluj meetup:
https://www.meetup.com/Ruby-a-la-Cluj/events/259590284/

Tudor Pavel

March 28, 2019
Tweet

More Decks by Tudor Pavel

Other Decks in Technology

Transcript

  1. GraphQL, what is it good for? Ruby a la Cluj,

    March 2019 Tudor Pavel https://youtu.be/ztZI2aLQ9Sw 1 / 45
  2. Who am I? Tudor. Full Stack dev @ PitechPlus since

    2013 Skillzes: Rails >= Ruby > SQL > React >= JavaScript > HTML/CSS > GraphQL > Java > Android > Arduino > C > PHP >> Rust > LISP > Haskell I use Emacs with evil-mode -> http://spacemacs.org/ 2 / 45
  3. What we'll be covering GraphQL intro What is it good

    for? (Common use cases) Demo some GraphQL Query vs. Command Authorization N+1 issues Bonus: versioning, tracing, caching, community 7 / 45
  4. Philosophy, reason for existing Alternative way to de ne your

    web APIs It began as an internal Facebook project in 2012 Bad Internet connectivity Many di erent mobile apps No more overfetching and underfetching Less round-trips and smaller payloads 9 / 45
  5. What it is A Query Language for APIs A more

    e cient alternative to REST Strongly typed contract for your API 10 / 45
  6. Avoid overfetching attributes Ask only for what you need. Request:

    query ArticleQuery { article(slug: "albert-article-9") { id description } } Response: { "data": { "article": { "id": "20", "description": "This is the description for the post." } } } 11 / 45
  7. Avoid underfetching dependencies query ArticleQuery { article(slug: "albert-article-9") { id

    description author { id username } } } { "data": { "article": { "id": "20", "description": "This is the description for the post.", "author": { "id": "2", "username": "Albert Pai" } } } } 12 / 45
  8. Many clients use case APIs that need exibility for many

    different clients Each client using same data in different ways We didn't have this 15 / 45
  9. Development work ow at project init Separate frontend and backend

    teams working in parallel to a contract (schema) We bene ted from this The frontend team implemented a mock API until backend was done Actual integration after 1 month was okay- ish 16 / 45
  10. Client-side caching Very dynamic UI which needs to update frequently

    with server push updates We had this 17 / 45
  11. Did I show the Rails code? I should show how

    it works behind the scenes. 20 / 45
  12. 2 main types of operations They often have di erent

    requirements. 1. Query returns data to the caller should be cacheable no validation errors needed GraphQL initially only had this 2. Command performs a mutable action needs validation errors GraphQL added these afterwards in the form of Mutations 22 / 45
  13. Mutations Also a Query in GraphQL, just a bit more

    special More or less a convention It's a Command with an immediate Query after 23 / 45
  14. Happy ow mutation SignInMutation { signInUser(input: { email: "[email protected]", password:

    "secret" }) { errors { path message } viewer { user { bio } } } } { "data": { "signInUser": { "errors": [], "viewer": { "user": { "bio": "Cofounder @GoThinkster, lived in Aol's HQ for a few months, kinda } } } } } 24 / 45
  15. Sad ow mutation SignInMutation { signInUser(input: { email: "[email protected]", password:

    "sad" }) { errors { path message } viewer { user { bio } } } } { "data": { "signInUser": { "errors": [ { "path": null, "message": "email or password is invalid" } ], "viewer": null } } } 25 / 45
  16. Authorization: GraphQL vs REST With REST, each request is an

    operation that can be authorized class PostsController < ApiController def create # First, check the client's permission level: if current_user.can?(:create_posts) # If the user is permitted, then perform the action: post = Post.create(params) render json: post else # Otherwise, return an error: render nothing: true, status: 403 end end end 27 / 45
  17. GraphQL needs something different This request-by-request mindset doesn't map well

    to GraphQL Only one controller and one action class GraphqlController < ApplicationController def execute # What permission is required for `query_str`? # It depends on the string! So, you can't generalize at this level. if current_user.can?(:"???") MySchema.execute(query_str, context: ctx, variables: variables) end end end 28 / 45
  18. What to do? We can consider Queries and Mutations separately

    for authorization. Mutations each mutation is like an API request in itself previous example of Posts#create will map to the createPost(...) mutation so each mutation should be authorized in its own right should return errors Queries each individual object can be like a GET request to a REST API so each object should be authorized for reading in its own right instead of errors, parts of the response will be null 29 / 45
  19. Authorization in Your Business Logic Single source of truth, easier

    testing See https://graphql.org/learn/authorization/ class Post < ActiveRecord::Base # Return the list of posts which `user` may see def self.posts_for(user) if user.admin? self.all else self.published end end end field :posts, [Types::Post], null: false def posts # Fetch the posts this user can see: Post.posts_for(context[:current_user]) end 30 / 45
  20. N+1 problems can arise easily in GraphQL where you can

    query nested objects it's not necessarily a N+1 SQL query problem, you could have di erent backend services for fetching each of the depdendent objects some sort of batched data loading is needed Facebook's DataLoader JS reference implementation: https://github.com/graphql/dataloader 32 / 45
  21. Example: Viewer feed homepage Each article has a favorite button

    which changes based on the viewer (current user) class Types::ArticleType < GraphQL::Schema::Object graphql_name 'Article' # ... field :viewer_has_favorited, Boolean, null: false def viewer_has_favorited current_user = context[:current_user] return false unless current_user # When multiple articles are queried, this will cause N+1 queries current_user.favorites.find_by(article_id: object.id).present? end end Note to self: show the Rails logs 33 / 45
  22. GraphQL::Batch to the rescue https://github.com/shopify/graphql-batch class Types::ArticleType < GraphQL::Schema::Object #

    ... def viewer_has_favorited # ... Loaders::FavoritesLoader.for(current_user).load(object.id) end end Note to self: show the Rails logs class Loaders::FavoritesLoader < GraphQL::Batch::Loader def initialize(current_user) @current_user = current_user end def perform(ids) favorite_article_ids = @current_user.favorites.where(article_id: ids).pluck(:a ids.each { |id| fulfill(id, favorite_article_ids.include?(id)) } end end 34 / 45
  23. Schema versioning Mark elds as deprecated with a message graphql-ruby

    doesn't have this yet :'( The idea is to use tracing to track which deprecated elds have stopped being used by clients so that they can be removed permanently Please, please avoid breaking changes in your APIs like the plague! 37 / 45
  24. Tracing and monitoring Important for tracking and improving performance of

    your GraphQL API Easily spot performance bottlenecks graphql-ruby has several integrations out-of-the-box Appsignal New Relic Scout Skylight Datadog Prometheus 38 / 45
  25. Caching Client-side caching Implemented by GraphQL clients Apollo Client has

    fetchPolicy Globally unique cache key for each Object, can be a combination of typename and ID I like to think of my data graph like an RTS game map with fog of war :) Server-side caching More tricky, but could be done with a similar per-Object strategy Cache invalidation is even trickier, a simple solution would be to use short cache expiration 41 / 45
  26. Community and tooling Thanks to its standardized spec, popularity, introspection

    and strongly-typed- ness, GraphQL has a ton of tools built around it. GraphiQL https://www.apollographql.com/ https://github.com/apollographql/graphql-tools https://hasura.io/all-features 42 / 45
  27. Recap We learned: GraphQL's reason for existing what GraphQL is

    what it isn't common use cases: many clients contract-based development dynamic UI Command vs. Query Authorization, N+1 Versioning, Tracing, Caching We will soon be deprecated as programmers thanks to tools like Hasura 43 / 45