Dataloader
Learn how to get the data efficiently without coupling our GraphQL API tightly to the SQL structure.
We'll cover the following...
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:
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:
defp deps do[{:dataloader, "~> 1.0.0"},# «Other deps»]end
Then in the Menu
context where we’ll define a Dataloader source:
def data() doDataloader.Ecto.new(Repo, query: &query/2)enddef query(queryable, _) doqueryableend
Let’s hop into iex -S mix
and play with this new library. First, we’ll create ourselves a Dataloader with a source:
# command 1alias PlateSlate.Menu# command 2source = Menu.data()# command 3loader = Dataloader.new |> Dataloader.add_source(Menu, source)
With that in place, we queue up some items to be loaded:
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:
loader |> Dataloader.get(Menu, Menu.Item, 2)# nil
To retrieve all queued-up batches, we use Dataloader.run/1
:
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:
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 ...