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/

Yusuke Mito

April 25, 2018
Tweet

More Decks by Yusuke Mito

Other Decks in Programming

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. GraphQLなら

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  10. RESTとの違いは?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. POST ! create
    GET ! show
    PATCH ! update
    DELETE ! destroy
    httpのメソッドやステータスをAPI表現に使わない
    200 ! OK
    400 ! Bad Request
    403 ! Forbidden
    404 ! Not Found

    View full-size slide

  14. Graphとは?

    View full-size slide

  15. 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

    View full-size slide

  16. クエリの書き⽅は?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  21. 実装⽅法は?

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  24. 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

    View full-size slide

  25. 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

    View full-size slide

  26. • QueryTypeの実装はcontrollerでやっていたことと
    ほぼ同じ
    • ObjectTypeの実装はほぼfieldを列挙するだけ
    • 型は⼀度定義すれば他の場所で使いまわせるので
    徐々に実装コストが下がっていく

    View full-size slide

  27. • 型ごとに公開できるフィールドをひたすら列挙して
    おけばクライアントが必要なものだけを取得できる
    ので、チーム間のコミュニケーションコストが下が

    • 型定義の中に型やfieldの意味を記述しておくとド
    キュメントに⾃動的に表⽰されるので、実装と仕様
    を⼀致させやすい
    field :name, String, "The unique name of this list", null: false

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  31. 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

    View full-size slide

  32. 無限にネストしたクエリ
    が書けてしまうのでは?

    View full-size slide

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

    View full-size slide