Dataloader

Learn how to get the data efficiently without coupling our GraphQL API tightly to the SQL structure.

Discovering Dataloader

Dataloader is a small package that defines a minimalist API for getting data in batches. We can use it within our contexts to keep all the SQL details nicely packed away, while still providing flexibility. It also won’t require us to have numerous context boilerplates either. Although Dataloader is managed under the absinthe-graphql GitHub organization, it is entirely separate from GraphQL itself. In fact, it has utilities to conveniently retrieve values inside ordinary Phoenix controllers:

Press + to interact
field :category, :category, resolve: dataloader(Menu)

Let’s get a basic Dataloader up and running, and then we’ll walk through what it does. First, let’s add it to our mix.exs file:

Press + to interact
defp deps do
[
{:dataloader, "~> 1.0.0"},
# «Other deps»
]
end

Then in the Menu context where we’ll define a Dataloader source:

Press + to interact
def data() do
Dataloader.Ecto.new(Repo, query: &query/2)
end
def query(queryable, _) do
queryable
end

Let’s hop into iex -S mix and play with this new library. First, we’ll create ourselves a Dataloader with a source:

Press + to interact
# command 1
alias PlateSlate.Menu
# command 2
source = Menu.data()
# command 3
loader = Dataloader.new |> Dataloader.add_source(Menu, source)

With that in place, we queue up some items to be loaded:

Press + to interact
loader = (
loader
|> Dataloader.load(Menu, Menu.Item, 1)
|> Dataloader.load(Menu, Menu.Item, 2)
|> Dataloader.load(Menu, Menu.Item, 3)
)

This doesn’t actually run any queries against the database yet. If we try to get one of those items out of the Dataloader, we’ll just get nil:

Press + to interact
loader |> Dataloader.get(Menu, Menu.Item, 2)
# nil

To retrieve all queued-up batches, we use Dataloader.run/1:

Press + to interact
loader = loader |> Dataloader.run

When we run that function, we can see that a single SQL query runs to grab all the items we’ve queued up so far. Now, if we use Dataloader.get/3 again, we’ll see that our items are here. We can also use Dataloader.get_many/3 to conveniently grab several items simultaneously:

Press + to interact
menu_item = loader |> Dataloader.get(Menu, Menu.Item, 2)
# %PlateSlate.Menu.Item{...}
menu_items = loader |> Dataloader.get_many(Menu, Menu.Item, [1,2,3])
# [%PlateSlate.Menu.Item{...}, ...]

The idea here is that we can load up one or more batches worth of data we want to retrieve, on one or more sources and delays the actual execution of any SQL queries until we need the results. When ...