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

GraphQL Q&A

GraphQL Q&A

Presentation slides for the JapanTaxi x MedPeer Ruby/Rails meetup
https://medpeer.connpass.com/event/82327/

80046ac4618076c5369398b92db8f313?s=128

Yusuke Mito

April 25, 2018
Tweet

Transcript

  1. GraphQL Q&A JapanTaxi SREチーム ⽔⼾祐介 @y_310

  2. JapanTaxiで次期APIに採⽤したGraphQLについて、 採⽤の理由とGraphQLがどういうものなのか紹介します

  3. なぜGraphQLを 採⽤するのか

  4. クライアント、サーバ両⽅の 課題を同時に解決できるから

  5. アプリエンジニア この画⾯に必要なデータを1回の リクエストで全部返してほしい サーバサイドエンジニア REST APIなので1APIあたり1つ のリソースしか返せません アプリエンジニア このフィールドはnullになる可能 性ありますか?

    サーバサイドエンジニア ちょっと実装を確認してきます…
  6. GraphQLなら

  7. None
  8. 関連の無いuserとrepositoryを同時に取得 クライアントが⾃分が必要とするリソースを⾃分で指定して取得できる 特定のクライアントに最適化したAPIを作る必要がなくなる

  9. GraphiQL https://developer.github.com/v4/explorer/

  10. 標準で⽤意されたAPIドキュメント スキーマ定義から⾃動⽣成され、 フィールドの型とnullabilityが明⽰ される スキーマからクライアントのモデルクラス を⾃動⽣成することも可能なのでインター フェイスの齟齬による問題が起きなくなる

  11. DEMO

  12. RESTとの違いは?

  13. POST /graphql エンドポイントは1つ

  14. POST ! create GET ! show PATCH ! update DELETE

    ! destroy 200 ! OK 400 ! Bad Request 403 ! Forbidden 404 ! Not Found
  15. POST ! create GET ! show PATCH ! update DELETE

    ! destroy httpのメソッドやステータスをAPI表現に使わない 200 ! OK 400 ! Bad Request 403 ! Forbidden 404 ! Not Found
  16. Graphとは?

  17. Post User Query user(id: 1): User post(id: 2): Post comments:

    [Comment] Post Post Post Comment Comment Comment posts: [Post] Comment Comment Comment comments: [Comment] User User user: User user: User user: User
  18. クエリの書き⽅は?

  19. rootオブジェクトはすべてのクエリの⼊ ⼝となるフィールドを持つ 1つのフィールドがそれぞれRESTにおける GET /users/:id GET /repositories/:id GET /licenses に相当する

  20. rootオブジェクトのフィールドに限らず 任意のフィールドが引数を取ることがで きる フィールドは引数を取ることができる

  21. Object型のフィールドは内包するフィー ルドを列挙する (必須) 必要なフィールドはすべて明⽰的に列挙 する必要がある

  22. フィールドがObject型の場合はネストし て書くことができる ⼦要素がある限り無限にネストできる user.owner.repositories.owner.repositories

  23. 実装⽅法は?

  24. graphql-ruby https://github.com/rmosolgo/graphql-ruby

  25. graphql-ruby • 作者はGitHubのエンジニア • GitHubのv4 APIの裏側で使われている (らしい) • Rails上で動くがRails⾃体との依存関係はほとんどない

  26. class Schema < GraphQL::Schema query Types::QueryType end class QueryType <

    GraphQL::Schema::Object field :user, Types::UserType, null: true do argument :id, ID, required: true end def user(id:) User.find_by(id: id) end end class UserType < GraphQL::Schema::Object field :email, String, null: true field :name, String, null: false field :friends, [User], null: false end Schema QueryType UserType
  27. class GraphqlController < ApplicationController def execute variables = ensure_hash(params[:variables]) query

    = params[:query] operation_name = params[:operationName] context = { current_user: current_user, } result = Schema.execute( query, variables: variables, context: context, operation_name: operation_name ) render json: result end end Schema.execute(query).as_json #=> Hash
  28. • QueryTypeの実装はcontrollerでやっていたことと ほぼ同じ • ObjectTypeの実装はほぼfieldを列挙するだけ • 型は⼀度定義すれば他の場所で使いまわせるので 徐々に実装コストが下がっていく

  29. • 型ごとに公開できるフィールドをひたすら列挙して おけばクライアントが必要なものだけを取得できる ので、チーム間のコミュニケーションコストが下が る • 型定義の中に型やfieldの意味を記述しておくとド キュメントに⾃動的に表⽰されるので、実装と仕様 を⼀致させやすい field

    :name, String, "The unique name of this list", null: false
  30. まとめ

  31. どういう場合に 採⽤すべき?

  32. • 複数のクライアントアプリケーションが存在する • API数が多い • ネストしたリソースを取得したい場⾯が多い • ドキュメントの作成コストを減らしたい

  33. おわり

  34. N+1問題はどうやって 解決する?

  35. graphql-batch class Types::CommentType < Types::BaseObject description 'Comment object' field :user,

    Types::Objects::UserType, null: false def user Loaders::RecordLoader.for(User).load(object.user_id) end end Loaderがidを内部で保持し、userの値を返す際に User.where(id: ids)で1クエリで結果を取得する https://github.com/Shopify/graphql-batch
  36. 無限にネストしたクエリ が書けてしまうのでは?

  37. max_complexity ⼿動で設定したcomplexityに応じて合計がmax_complexityを上回るとエラー max_depth クエリのネストの深さを制限、超えるとエラー