使用graphql-ruby中的解析器来保持查询的简洁性
简而言之
使用graphql-ruby时,query_type.rb文件往往会变得过于臃肿。
我正在查看GitHub的Issue时,看到了有关在query_type.rb中使用Resolver来尽量避免肥胖化的最佳实践。这个实践似乎没有被反映在官方指南中,所以我想分享一下。
参考问题:
https://github.com/rmosolgo/graphql-ruby/issues/1825#issuecomment-441306410
顺便提一下,确认的版本如下所示。
另外,我们假定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的模式。