Как я могу заставить работать Absinthe и Dataloader вместе?

У меня есть GraphQL api, который отлично работает с обычными функциями разрешения. Моя цель - устранить проблему N + 1.

Для этого я решил использовать Dataloader. Я сделал эти шаги, предположительно, запустив приложение:


  1. Я добавил эти две функции в свой контекстный модуль:
defmodule Project.People do
  # CRUD...

  def data, do: Dataloader.Ecto.new(Repo, query: &query/2)

  def query(queryable, _params) do
    queryable
  end
end
  1. Я добавил context/1 и plugins/0 в модуль схемы и обновил резолверы для запросов:
defmodule ProjectWeb.GraphQL.Schema do
  use Absinthe.Schema

  import Absinthe.Resolution.Helpers, only: [dataloader: 1]

  alias ProjectWeb.GraphQL.Schema
  alias Project.People

  import_types(Schema.Types)

  query do
    @desc "Get a list of all people."
    field :people, list_of(:person) do
      resolve(dataloader(People))
    end

    # Other queries...
  end

  def context(context) do
    loader =
      Dataloader.new()
      |> Dataloader.add_source(People, People.data())

    Map.put(context, :loader, loader)
  end

  def plugins, do: [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
end

Никаких других шагов в официальных руководствах не приводится. Мой объект :person выглядит так:

@desc "An object that defines a person."
  object :person do
    field :id, :id
    field :birth_date, :date
    field :first_name, :string
    field :last_name, :string
    field :pesel, :string
    field :second_name, :string
    field :sex, :string

    # field :addresses, list_of(:address) do
    #   resolve(fn parent, _, _ ->
    #     addresses = Project.Repo.all(Ecto.assoc(parent, :addresses))

    #     {:ok, addresses}
    #   end)
    #   description("List of addresses that are assigned to this person.")
    # end

    # field :contacts, list_of(:contact) do
    #   resolve(fn parent, _, _ ->
    #     contacts = Project.Repo.all(Ecto.assoc(parent, :contacts))

    #     {:ok, contacts}
    #   end)
    #   description("List of contacts that are assigned to this person.")
    # end
  end

Закомментированная часть - это преобразователь, который работает без загрузчика данных и не вызывает проблемы.

Когда я пытаюсь запросить:

{
  people { 
    id
  }
}

Я получаю это:

Request: POST /graphiql
** (exit) an exception was raised:
    ** (Dataloader.GetError)   The given atom - :people - is not a module.

  This can happen if you intend to pass an Ecto struct in your call to
  `dataloader/4` but pass something other than a struct.

Я не полностью понимаю сообщение об ошибке, так как я передаю модуль dataloader/1 и не могу найти решение. В чем может быть дело?


person bart-kosmala    schedule 13.11.2020    source источник


Ответы (1)


Мне удалось заставить это работать - вот как:

Загрузчик данных сам по себе не знает, что извлечь из базы данных, он понимает только ассоциации. Таким образом, dataloader(People) может быть только частью блока object, но не блока query.

Другими словами:

defmodule ProjectWeb.GraphQL.Schema do
  use Absinthe.Schema

  import Absinthe.Resolution.Helpers, only: [dataloader: 1]

  alias ProjectWeb.GraphQL.Schema
  alias Project.People

  import_types(Schema.Types)

  query do
    @desc "Get a list of all people."
    field :people, list_of(:person) do
      resolve(&StandardPerson.resolver/2)
    end

    # Other queries...
  end

  def context(context) do
    loader =
      Dataloader.new()
      |> Dataloader.add_source(People, People.data())

    Map.put(context, :loader, loader)
  end

  def plugins, do: [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
end

а также

@desc "An object that defines a person."
  object :person do
    field :id, :id
    field :birth_date, :date
    field :first_name, :string
    field :last_name, :string
    field :pesel, :string
    field :second_name, :string
    field :sex, :string

    field :addresses, list_of(:address) do
      resolve(dataloader(People))
      description("List of addresses that are assigned to this person.")
    end

    field :contacts, list_of(:contact) do
      resolve(dataloader(People))
      description("List of contacts that are assigned to this person.")
    end
  end
person bart-kosmala    schedule 13.11.2020