Dataloader
View SourceMaybe you like good performance, or you realized that you are filling your objects with fields that need resolvers like
@desc "A user of the blog"
object :user do
field :id, :id
field :name, :string
field :contacts, list_of(:contact)
field :posts, list_of(:post) do
arg :date, :date
resolve &Resolvers.Content.list_posts/3
end
endThis is going to get tedious and error-prone very quickly what if we could support a query that supports associations like
@desc "A user of the blog"
object :user do
field :id, :id
field :name, :string
field :contacts, list_of(:contact)
field :posts, list_of(:post) do
arg :date, :date
resolve dataloader(Content)
end
endThis way associations are all handled in the context business logic aware conditions, to support this is actually surprisingly simple.
Since we had already setup users to load associated posts we can change that to use dataloader to illustrate how much simpler this gets.
Let's start by adding dataloader as a dependency in mix.exs:
defp deps do
[
{:dataloader, "~> 1.0.7"}
<< other deps >>
]Next, we need to set up dataloader in our context which allows us to load associations using rules:
In lib/blog/content.ex:
def data(), do: Dataloader.Ecto.new(Repo, query: &query/2)
def query(queryable, params) do
queryable
endThis sets up a loader that can use pattern matching to load different rules for different queryables, also note this function is passed in the context as the second parameter and that can be used for further filtering.
Then let's add a configuration to our schema (in lib/blog_web/schema.ex) so that we can allow Absinthe to use Dataloader:
defmodule BlogWeb.Schema do
use Absinthe.Schema
def context(ctx) do
loader =
Dataloader.new()
|> Dataloader.add_source(Content, Content.data())
Map.put(ctx, :loader, loader)
end
def plugins do
[Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
end
# << rest of the file>>The loader is all set up, now let's modify the resolver to use Dataloader. In lib/blog_web/schema/account_types.ex modify the user object to look as follows:
@desc "A user of the blog"
object :user do
field :id, :id
field :name, :string
field :contacts, list_of(:contact)
field :posts, list_of(:post) do
arg :date, :date
resolve dataloader(Content)
end
endThat's it! You are now loading associations using Dataloader
More Examples
While the above examples are simple and straightforward we can use other strategies with loading associations consider the following:
object :user do
field :posts, list_of(:post), resolve: fn user, args, %{context: %{loader: loader}} ->
loader
|> Dataloader.load(Blog, :posts, user)
|> on_load(fn loader ->
{:ok, Dataloader.get(loader, Blog, :posts, user)}
end)
endIn this example, we are passing some args to the query in the context where our source lives. For example, this function now receives args as params meaning we can do now do fun stuff like apply rules to our queries like the following:
def query(query, %{has_admin_rights: true}), do: query
def query(query, _), do: from(a in query, select_merge: %{street_number: nil})Check out the docs for more non-trivial ways of using Dataloader.