0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GraphQLとMutationの基本 - Rails MVCとの対比

0
Posted at

背景

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 がやっていること:

  1. params[:query] のGraphQL文字列をパースする
  2. mutation名(例: createOrder)から対応するMutationクラスを特定する
  3. そのクラスの resolve メソッドを実行する
  4. 結果を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 に相当)

感想

  • すごく便利なんだけど、ただ慣れてなくて使いこなせないというのは勿体無いなぁ

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?