GraphQL Rubyを使ったときのデータの流れ
この記事は iCARE Dev Advent Calendar 2021(レーン1) の 23日目の記事です。
こんにちは、iCAREサーバーサイドエンジニアの寺井(@krpk1900_dev)です。
私はエンジニアとしては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