使用graphql-ruby中的解析器来保持查询的简洁性

简而言之

使用graphql-ruby时,query_type.rb文件往往会变得过于臃肿。

我正在查看GitHub的Issue时,看到了有关在query_type.rb中使用Resolver来尽量避免肥胖化的最佳实践。这个实践似乎没有被反映在官方指南中,所以我想分享一下。

参考问题:
https://github.com/rmosolgo/graphql-ruby/issues/1825#issuecomment-441306410

顺便提一下,确认的版本如下所示。

ライブラリバージョンruby2.6.5graphql-ruby1.19.5

另外,我们假定GraphQL响应将返回用户单个和列表。

用户模型如下所示。

create_table "users", force: :cascade do |t|
  t.string "email"
  t.string "password"
  t.string "first_name"
  t.string "last_name"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
end

顺便说一下,这里用于显示的UserType假设如下。

module Types
  class UserType < BaseObject
    field :id, Int, null: false
    field :email, String, null: false
    field :password, String, null: false
    field :first_name, String, null: true
    field :last_name, String, null: true
  end
end

一般的查询

在graphql-ruby中,如果按照一般方式编写query_type,可能会如下所示。

module Types
  class QueryType < BaseObject
    field :users, Types::UserType.connection_type, null: false do
      argument :id, Int, required: false
      argument :email, String, required: false
      argument :first_name, String, required: false
      argument :last_name, String, required: false
    end
    def users(**args)
      res = User.all
      args.each do |k, v|
        # argumentsはGraphQLの仕様上、keyがキャメルケースになる。argsにはスネークケースで入っている
        argument = self.class.arguments[k.to_s.camelize(:lower)]
        # 各argumentで絞り込む
        res = res.where("#{k} = ?", v)
      end
      res
    end

    field :user, Types::UserType, null: false do
      argument :id, Int, required: true
    end
    def user(id:)
      User.find(id)
    end
  end
end

如果要自定义输出数据,就必须在query_type自身上进行实现。

当然,可能还可以更简单地描述,但无论如何,由于必须在query_type.rb中记录所有与READ相关的查询,随着查询类型的增加,视野会变得更加混乱。

使用Resolver进行查询。

在 Query 中有一个 resolve 选项,通过将处理转发到 Resolver 来将处理外置。

module Types
  class QueryType < BaseObject
    field :users, resolver: Resolvers::UserConnectionResolver
    field :user, resolver: Resolvers::UserResolver
  end
end

就查询类型而言,query_type.rb变得相当简单且易于理解了。

当然,需要在Resolver中实现具体的返回内容。
然而,只需要将query_type.rb中的处理移至Resolver。

module Resolvers
  class UserConnectionResolver < GraphQL::Schema::Resolver
    type UserType.connection_type, null: false

    argument :id, Int, required: false
    argument :email, String, required: false
    argument :first_name, String, required: false
    argument :last_name, String, required: false

    def resolve(**args)
      res = User.all
      args.each do |k, v|
        argument = self.class.arguments[k.to_s.camelize(:lower)]
        res = res.where("#{k} = ?", v)
      end
      res
    end
  end
end
module Resolvers
  class UserResolver < GraphQL::Schema::Resolver
    type UserType, null: false

    argument :id, Int, required: true

    def resolve(id:)
      User.find(id)
    end
  end
end

通过将处理作为解析器剥离出来,可以将query_type.rb文件变为执行GraphQL路由的文件,并在每个resolver.rb文件中像控制器一样实现处理逻辑,从而达到这样的组织结构。

需要注意Resolver的使用吗?

尽管我介绍了一些Resovler的模式,但根据官方指南,是否真的有必要使用Resolver以及对于Resolver的使用是否持怀疑态度。

    • テストがしづらくなる

 

    • graphql-rubyの更新

 

    Resolverが肥大化していってしまう

似乎有人对此表示担忧。

由于在参考问题中,作者承认了这些介绍的技巧是有效的,并要求更新文档,因此未来可能会更新指南。

在任何情况下都不一定需要使用Resolver,所以在应用到其他情况时需要小心。

這是一個介紹使用resolver的模式。

bannerAds