GraphQL Rubyを使ったときのデータの流れ | Dev Driven 開発・デザインチーム GraphQL Rubyを使ったときのデータの流れ | 働くひとと組織の健康を創る iCARE

BLOG

GraphQL Rubyを使ったときのデータの流れ

2021/12/23

この記事は iCARE Dev Advent Calendar 2021(レーン1) の 23日目の記事です。

こんにちは、iCAREサーバーサイドエンジニアの寺井です。
私はエンジニアとしてはiCAREが1社目で、入社前はRailsの学習をしていましたがREST APIの学習経験しかありませんでした。
そのため、GraphQLを用いたデータフローのキャッチアップには入社以来最も苦戦してきました。

そこで今回は、GraphQL Rubyを使ったときのデータフローについて簡単に書いて行きたいと思います。

エントリーポイントからRailsのControllerまで

GraphQL Rubyとは、GraphQLのAPIサーバをRubyのコードで記述するためのgemです。
この記事では、フロントエンドから「データをください」というリクエストが投げられた後からスタートして、どんな流れでサーバーサイドまで到達しているのかを順にたどっていきたいと思います。

例として、フロントエンドでCustomer一覧のデータをサーバーサイドから取得したい場合を考えてみます。
まずは下記のような「Customer一覧のデータをください」というQueryをフロントエンドで発行します。

query {
  customers {
    id
    name
    gender
  }
}

ちなみに「Customersのデータを更新してください」や「Customersのデータを削除してください」というリクエストのことはGraphQLではMutationと呼ばれます。

GraphQLはREST APIとは異なりエンドポイントは固定で1つのみです。

POST /graphql

先ほどのQueryの文字列を圧縮してqueryパラメータに入れることによって、そのHTTPリクエストがどのようなQueryなのかの情報を持たせています。

http://hoge/graphql?query={customers{id,name,gender}}

このあたりはREST APIを使用しているときと共通で、Railsがルーティングを行い、iCAREの場合ではgraphql_controller#executeに到達します。

Query一覧と型

先ほど実行したcustomers Queryは、app/graphql/types/query_type.rbというQuery一覧を記述するファイルに定義されています。

module Types
 class QueryType < Types::BaseObject
   # 省略
   # Queryの定義がたくさんある
   field :customers, Types::CustomersType, null: false do
     argument :name, String, required: false
     argument :id, String, required: false
     argument :genders, [Types::Customer::GenderFieldType], required: false
   end

def customers(**args)
     Queries::CustomersQuery.new.policy_scoped_users(args)
   end
 # 省略
 end
end

このQueryが返す値の型をType::CustomersTypeで、このQueryに渡す引数をargumentで指定しています。
def customerでは実際に返す値を決める処理を行っています。
上記の例では、引数で渡したdepartment_idsやuuid、gendersを使ってポリシースコープに当てはめて、権限のあるCustomerのみを返り値としています。

では型はどこで定義されているのかと言うと、app/graphql/types/customers_type.rbというcustomers Queryの型を記述する専用のファイルに定義されていました。

module Types
  class CustomersType < Types::BaseObject
    field_class Types::BaseField

    field :name, String, required: false
    field :id, String, required: false
    field :genders, [Types::Customer::GenderFieldType], required: falseeld :gender, String, null: false
  end
end

customersオブジェクトがこのファイルで定義されているCustomersTypeに合致しているかをチェックしてくれています。

controllerからQueryが定義されているファイルまで

QueryとTypeの話を間にしてしまいましたが、graphql_controllerに戻りたいと思います。

# 上でパラメータの保存などを行っている
result= MyAppSchema.execute(
  params[:query],
  variables: params[:variables],
  context: { current_user: current_user },
)
render json: result

graphql_controller#executeでは、パラメータの保存などを行ったとにMyAppSchema#executeを実行しています。

では次に、MyAppSchemaを見に行きましょう。

classMySchema< GraphQL::Schema
  query Types::Query
  mutation Types::Mutation
end

my_app_schema.rbを見てみると、先ほど出てきたapp/graphql/types/query_type.rbに記述していたQuery一覧を読み込んでいるようです。

customers Queryを実行したときはここで読み込んだQuery一覧を元にして、私が普段コードの追加や修正をするときに実際に編集しているファイルであるapp/graphql/queries/customers_query.rbに到達するという流れのようです。

module Queries
  class CustomersQuery < Queries::BaseObject
    def customers(arguments: {})
      # 返り値を決定するまでの処理
      if arguments
            # 引数も参照できる
      end
            # 省略 
      {
        name: name,
        id: id,
        genders: genders
      }
    end
  end
end

セクションタイトル

サーバーサイドからのレスポンスがフロントエンドに到達するまでのデータの流れはこの記事では書きませんが、この記事で見てきた流れと逆のルートで受け渡しされていると思います。

query {
  customers {
    id
    name
    gender
  }
}

こうして最終的に、例として挙げていた上記のリクエストに対して下記のような形式のレスポンスが返ってくることになります。

{
  "data": {
    "customers": [
      {
        "id": 1,
        "name": "terai",
        "gender": "male"
      },
          {
        "id": 2,
        "name": "takei",
        "gender": "male"
      },
            {
        "id": 3,
        "name": "niwa",
        "gender": "male"
      }
    ]
  }
}

最後に

冒頭でも書いたように、GraphQL Rubyは私が今一番苦戦している技術です。
それに加えてこの記事はアドベントカレンダーの記事だったので時間に追われてしまい、調査も十分にできずに投稿することになってしまいました。
おそらく間違っている箇所もあり責任のある情報の発信ができなかったことは申し訳なく感じていますが、今年の締めくくりに自分が一番苦手意識を持っている技術について書くと決めたことで、GraphQL Rubyについて調べたり自分の認識を確かめることができて有意義な時間にできました。
大きな間違いから小さな指摘まで何でも大歓迎ですので、何かお気づきの点があればコメントで教えていただきたいと思います。

iCARE Dev Advent Calendar 2021(レーン1) の明日の記事は、超努力家デザイナーのじゃっきーさんです!
お楽しみに!


※この記事は以下のサイトを参考にさせていただきました。
https://graphql-ruby.org/
https://github.com/rmosolgo/graphql-ruby
https://zenn.dev/necocoa/articles/setup-graphql-ruby
https://qiita.com/dkawabata/items/4fd965ee6d7295386a8b
https://qiita.com/k-penguin-sato/items/07fef2f26fd6339e0e69
https://qiita.com/shunp/items/d85fc47b33e1b3a88167