名称: absinthe-resolvers 用户可调用: false 描述: 在使用Absinthe实现GraphQL解析器时使用。涵盖解析器模式、dataloader集成、批处理和错误处理。
Absinthe - 解析器
在Absinthe中实现高效且可维护解析器的指南。
关键概念
基本解析器
defmodule MyApp.Resolvers.User do
alias MyApp.Accounts
def list_users(_parent, args, _resolution) do
{:ok, Accounts.list_users(args)}
end
def get_user(_parent, %{id: id}, _resolution) do
case Accounts.get_user(id) do
nil -> {:error, "用户未找到"}
user -> {:ok, user}
end
end
def create_user(_parent, %{input: input}, _resolution) do
case Accounts.create_user(input) do
{:ok, user} -> {:ok, user}
{:error, changeset} -> {:error, changeset}
end
end
end
解析上下文
def current_user(_parent, _args, %{context: %{current_user: user}}) do
{:ok, user}
end
def current_user(_parent, _args, _resolution) do
{:error, "未认证"}
end
Dataloader集成
defmodule MyApp.Schema do
use Absinthe.Schema
def context(ctx) do
loader =
Dataloader.new()
|> Dataloader.add_source(MyApp.Repo, Dataloader.Ecto.new(MyApp.Repo))
Map.put(ctx, :loader, loader)
end
def plugins do
[Absinthe.Middleware.Dataloader] ++ Absinthe.Plugin.defaults()
end
end
# 在类型定义中
object :user do
field :posts, list_of(:post) do
resolve dataloader(MyApp.Repo)
end
end
自定义Dataloader源
defmodule MyApp.Loaders.User do
def data() do
Dataloader.KV.new(&fetch/2)
end
defp fetch(:posts_count, user_ids) do
counts = MyApp.Posts.count_by_user_ids(user_ids)
Map.new(user_ids, fn id ->
{id, Map.get(counts, id, 0)}
end)
end
end
最佳实践
- 使用Dataloader - 防止N+1查询
- 保持解析器简洁 - 委托给上下文模块
- 优雅处理错误 - 返回有意义的错误消息
- 使用中间件 - 用于跨领域关注点如认证
- 批量相关查询 - 使用dataloader批处理
中间件
defmodule MyApp.Middleware.Auth do
@behaviour Absinthe.Middleware
def call(resolution, _config) do
case resolution.context do
%{current_user: %{}} ->
resolution
_ ->
resolution
|> Absinthe.Resolution.put_result({:error, "未授权"})
end
end
end
# 应用于字段
field :admin_data, :string do
middleware MyApp.Middleware.Auth
resolve &MyApp.Resolvers.Admin.get_data/3
end
错误处理
defmodule MyApp.Resolvers.Helpers do
def handle_changeset_errors(%Ecto.Changeset{} = changeset) do
errors =
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", to_string(value))
end)
end)
{:error, errors}
end
end
反模式
- 避免在解析器中包含业务逻辑
- 不要在解析器中直接查询数据库
- 避免返回原始的Ecto changesets
- 不要跳过错误处理