背景
Rails MVCでのCRUD開発に慣れている前提で、GraphQLの「Mutation」とは何か、どこから呼ばれてどう処理されるのかを、REST APIとの対比で整理する
学んだこと・気づき
1. GraphQL Mutation = データ変更用のコントローラーアクション
| MVC (REST) | GraphQL | 役割 |
|---|---|---|
routes.rb |
schema |
URLとアクションの対応付け |
Controller の create/update
|
Mutation | データを変更する処理 |
Controller の show/index
|
Query | データを取得する処理 |
| Strong Parameters | argument |
受け取るパラメータの定義 |
GraphQLでは「データを書き換える操作」と「データを読み取る操作」が明確に分離されている。RESTでいうGET系がQuery、POST/PATCH/DELETE系がMutationに対応する
2. resolve メソッド = アクションメソッド本体
Mutationクラスの resolve メソッドが、コントローラーでいう def create の中身に相当する
# Mutation(GraphQL版)
# app/graphql/mutations/create_order.rb
module Mutations
class CreateOrder < BaseMutation
# argument = params.require / permit に相当
argument :product_id, ID, required: true
argument :quantity, Integer, required: true
# 戻り値の型定義(graphql-ruby標準の書き方)
field :order, Types::OrderType, null: true
field :errors, [String], null: false
def resolve(product_id:, quantity:)
# ここがコントローラーアクションの中身と同じ
product = Product.find(product_id)
order = current_user.orders.build(product: product, quantity: quantity)
if order.save
{ order: order, errors: [] }
else
{ order: nil, errors: order.errors.full_messages }
end
end
end
end
# REST版で書くとこういうイメージ
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
product = Product.find(params[:product_id])
order = current_user.orders.build(product: product, quantity: params[:quantity])
if order.save
render json: order, status: :created
else
render json: { errors: order.errors.full_messages }, status: :unprocessable_entity
end
end
end
ポイント:
-
argumentはStrong Parametersのpermitに相当する。型と必須/任意を宣言的に定義できる -
fieldで戻り値の型を定義する。RESTのレスポンスJSONの構造を明示的に宣言しているイメージ -
resolveメソッドの引数はキーワード引数として受け取る。params[:product_id]ではなくproduct_id:で直接受け取れるので、型安全性が高い
3. ルーティング定義(たった1行)
# config/routes.rb
post '/graphql', to: 'graphql#execute'
RESTなら resources :orders のように個別にルーティングを書くが、GraphQLはこの1行だけで全Mutation/Queryを受け付ける
RESTでは新しいリソースが増えるたびに routes.rb にルーティングを追加する必要があるが、GraphQLではMutationクラスを追加してスキーマに登録するだけでよい
4. ApplicationSchema.execute の役割
# app/controllers/graphql_controller.rb
class GraphqlController < ApplicationController
def execute
result = ApplicationSchema.execute(
params[:query], # ← GraphQL文字列(どのMutationを呼ぶか)
variables: params[:variables], # ← 引数(productId, quantityなど)
context: { current_user: current_user }
)
render json: result
end
end
ApplicationSchema.execute がやっていること:
-
params[:query]のGraphQL文字列をパースする - mutation名(例:
createOrder)から対応するMutationクラスを特定する - そのクラスの
resolveメソッドを実行する - 結果をJSON形式で返す
RESTでいうと routes.rb のルーティング解決 + コントローラーのdispatchに相当する処理を、このメソッド1つでやっている
5. Mutationのスキーマへの登録
Mutationクラスを作っただけでは使えない。スキーマの MutationType に登録する必要がある
# app/graphql/types/mutation_type.rb
module Types
class MutationType < Types::BaseObject
field :create_order, mutation: Mutations::CreateOrder
end
end
# app/graphql/application_schema.rb
class ApplicationSchema < GraphQL::Schema
mutation(Types::MutationType)
query(Types::QueryType)
end
RESTでいう routes.rb にルーティングを追加する行為が、この MutationType へのfield登録に相当する。graphql-ruby は MutationType に登録された field 名(create_order → キャメルケース変換で createOrder)を使って、リクエストのmutation名とMutationクラスをマッチングする
6. リクエストの全体フロー
【REST】
ブラウザ → POST /orders
→ routes.rb でURLごとに振り分け
→ OrdersController#create
【GraphQL】
フロントエンド → POST /graphql(全部ここ)
→ routes.rb → GraphqlController#execute
→ ApplicationSchema.execute がquery文字列を解析して振り分け
→ Mutations::CreateOrder#resolve
RESTはURLで振り分け、GraphQLはリクエストボディの中身で振り分ける
GraphQLのリクエストボディは以下のような構造になっている
{
"query": "mutation createOrder($productId: ID!, $quantity: Int!) { createOrder(input: { productId: $productId, quantity: $quantity }) { order { id status } errors } }",
"variables": {
"productId": "123",
"quantity": 2
}
}
このJSON内の query 文字列を ApplicationSchema.execute がパースして、どのMutationクラスの resolve を呼ぶかを決定する
結論
- GraphQLのMutationは、RESTのコントローラーアクション(create/update/delete)に相当する概念
-
resolve= アクションメソッド本体、argument= Strong Parameters - ルーティングは
POST /graphqlの1行のみで、ApplicationSchema.executeが内部でmutation名を見て振り分ける - Mutationクラスを作ったら
MutationTypeに登録する必要がある(RESTのroutes.rbに相当)
感想
- すごく便利なんだけど、ただ慣れてなくて使いこなせないというのは勿体無いなぁ
参考
- graphql-ruby 公式ドキュメント — graphql-ruby の公式リファレンス
- graphql-ruby Mutations — Mutationクラスの定義方法
- GraphQL 公式 — GraphQL仕様のMutationについて