View Source Absinthe.Middleware.Batch (absinthe v1.7.0)
Batch the resolution of multiple fields.
Motivation
Consider the following graphql query:
{
posts {
author {
name
}
}
}
posts
returns a list of post
objects, which has an associated author
field.
If the author
field makes a call to the database we have the classic N + 1 problem.
What we want is a way to load all authors for all posts in one database request.
This plugin provides this, without any eager loading at the parent level. That is,
the code for the posts
field does not need to do anything to facilitate the
efficient loading of its children.
Example Usage
The API for this plugin is a little on the verbose side because it is not specific to any particular batching mechanism. That is, this API is just as useful for an Ecto based DB as it is for talking to S3 or the File System. Thus we anticipate people (including ourselves) will be creating additional functions more tailored to each of those specific use cases.
Here is an example using the Absinthe.Resolution.Helpers.batch/3
helper.
object :post do
field :name, :string
field :author, :user do
resolve fn post, _, _ ->
batch({__MODULE__, :users_by_id}, post.author_id, fn batch_results ->
{:ok, Map.get(batch_results, post.author_id)}
end)
end
end
end
def users_by_id(_, user_ids) do
users = Repo.all from u in User, where: u.id in ^user_ids
Map.new(users, fn user -> {user.id, user} end)
end
Let's look at this piece by piece:
{__MODULE__, :users_by_id}
: is the batching function which will be used. It must be a 2 arity function. For details see thebatch_fun
typedoc.post.author_id
: This is the information to be aggregated. The aggregated values are the second argument to the batching function.fn batch_results
: This function takes the results from the batching function. it should return one of the resolution function values.
Link to this section Summary
Functions
Callback implementation for Absinthe.Plugin.after_resolution/1
.
Callback implementation for Absinthe.Plugin.before_resolution/1
.
Callback implementation for Absinthe.Middleware.call/2
.
Callback implementation for Absinthe.Plugin.pipeline/2
.
Link to this section Types
Specs
The function to be called with the aggregate batch information.
It comes in both a 2 tuple and 3 tuple form. The first two elements are the module and function name. The third element is an arbitrary parameter that is passed as the first argument to the batch function.
For example, one could parameterize the users_by_id
function from the moduledoc
to make it more generic. Instead of doing {__MODULE__, :users_by_id}
you could do
{__MODULE__, :by_id, User}
. Then the function would be:
def by_id(model, ids) do
model
|> where([m], m.id in ^ids)
|> Repo.all()
|> Map.new(&{&1.id, &1})
end
It could also be used to set options unique to the execution of a particular batching function.
Specs
post_batch_fun() :: (term() -> Absinthe.Type.Field.result())
Link to this section Functions
Callback implementation for Absinthe.Plugin.after_resolution/1
.
Callback implementation for Absinthe.Plugin.before_resolution/1
.
Callback implementation for Absinthe.Middleware.call/2
.
Callback implementation for Absinthe.Plugin.pipeline/2
.