3
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?

More than 3 years have passed since last update.

GraphQLAdvent Calendar 2021

Day 11

fieldにresolverを当てて擬似的にqueryをネストする

Last updated at Posted at 2021-12-10

はじめに

GraphQLの利点として「必要なデータを一括取得できる」という点があると思います。
RESTfulなAPIとの違いという意味も含めて、
今回はqueryを組み合わせて依存関係にあるデータを一括取得する方法を書きたいと思います。

あくまで設計指針という内容なので、具体的な環境構築方法は趣旨からはずれるため割愛します。

使うもの

  • 言語: Ruby2.7
  • Gem: graphql-ruby, graphql-rails

やりたいこと

例えば以下の2つのqueryを同時に呼びたいとします

{
  customer(name: "hoge") {
    id
  }
}

{
  invoice(customerId: 1, status: "open") {
    price
  }
}

ですが、customerIdを知るにはcustomer queryを叩かなければならないため、
愚直にやるとcustomer queryを叩いてcustomerIdを取得してからinvoice queryを叩く必要があると思います。

なら以下のようにすればいいと思うかもしれません。

{
  customer(name: "hoge") {
    id
    invoice(status: "open") {
      price
    }
  }
}

しかし、これだと「customerIdを知っている状態でinvoice queryを単体で呼びたい時」に困ります。
せっかくcustomerIdを知っている状態でcustomer queryを呼ぶとOver-fetchingになりますよね。
そのため、queryはqueryで残したいと思います。

解決策

以下のようにcustomer queryのinvoice fieldにcustomerIdを渡すことでqueryをネストしたようにできます。

{
  customer(name: "hoge") {
    id
    name
    # invoice queryと同じ
    invoice(customerId: null, status: "open") {
      price
    }
  }
}

# query単体でも叩ける
{
  invoice(customerId: 1, status: "open") {
    price
  }
}

メリット

このようにすることで、queryをそのままobject typeのfieldに充てることができます。
具体的にはこの後の実装を見ていただきたいです。

実装の具体例

今回はgraphql-rubyを使用します。
query typeは以下のように定義します。

types/query_type.rb
module Types
  class QueryType < Types::BaseObject
    field :customer, resolver: Resolvers::CustomerResolver
    field :invoice, resolver: Resolvers::InvoiceResolver
  end
end

graphql-rubyでは基本的にresolverをクラス単位で作成します。

resolvers/customer_resolver.rb
module Resolvers
  class CustomerResolver < BaseResolver
    type Types::Object::CustomerType, null: true

    def resolve(name:)
      Customer.find_by(name: name)
    end
  end
end

ここでCustomerTypeのinvoice fieldにもresolverを充てます。

types/object/customer_type.rb
module Types::Object
  class CustomerType < Types::BaseObject
    field :id, ID, null: false
    field :name, String, null: false
    # queryと同じように定義できる
    field :invoice , resolver: Resolvers::InvoiceResolver
  end
end

InvoiceResolverはというと
object(Customerインスタンス)からinvoiceをたどることも
引数のcustomer_idからinvoiceを取得することもできるようにします。

resolvers/invoice_resolver.rb
module Resolvers
  class InvoiceResolver < BaseResolver
    type Types::Object::InvoiceType, null: true
		
    argument :customer_id, ID, required: false
    argument :status, String, required: true

    def resolve(customer_id:, status:)
      raise ArgumentError if object.nil? && product_id.nil?

      # 引数の有無で判定
      customer_id ? Invoice.find_by(customer_id: customer_id) : object.invoice
    end
  end
end

まとめ

これでqueryとqueryをネストできているはずです。
このようにquery同士も組み合わせることができるところがGraphQLの魅力かと思いました。
リクエストを一つにまとめることもできますし、customer_idのように一度保持した値から単体でクエリを叩いて必要な分だけ取得するというNot Over-fetchingなところが強力ですね。

3
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
3
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?