absinthe v1.4.12 Absinthe.Middleware.Batch View Source

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 the batch_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.

Clearly some of this could be derived for ecto functions. Check out the Absinthe.Ecto library for something that provides this:

field :author, :user, resolve: assoc(:author)

Such a function could be easily built upon the API of this module.

Link to this section Summary

Types

The function to be called with the aggregate batch information

Functions

callback to do something with the resolution accumulator after resolution

callback to setup the resolution accumulator prior to resolution

This is the main middleware callback

callback used to specify additional phases to run

Link to this section Types

Link to this type batch_fun() View Source
batch_fun() :: {module(), atom()} | {module(), atom(), term()}

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.

Link to this type post_batch_fun() View Source
post_batch_fun() :: (term() -> Absinthe.Type.Field.result())

Link to this section Functions

callback to do something with the resolution accumulator after resolution.

NOTE: This function is given the full accumulator. Namespacing is suggested to avoid conflicts.

Callback implementation for Absinthe.Plugin.after_resolution/1.

callback to setup the resolution accumulator prior to resolution.

NOTE: This function is given the full accumulator. Namespacing is suggested to avoid conflicts.

Callback implementation for Absinthe.Plugin.before_resolution/1.

This is the main middleware callback.

It receives an %Absinthe.Resolution{} struct and it needs to return an %Absinthe.Resolution{} struct. The second argument will be whatever value was passed to the middleware call that setup the middleware.

Callback implementation for Absinthe.Middleware.call/2.

Link to this function pipeline(pipeline, exec) View Source

callback used to specify additional phases to run.

Plugins may require additional resolution phases to be run. This function should use values set in the resolution accumulator to determine whether or not additional phases are required.

NOTE: This function is given the whole pipeline to be inserted after the current phase completes.

Callback implementation for Absinthe.Plugin.pipeline/2.