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

#graphql #elixir #ecto #dataloader #absinthe

#graphql #эликсир #ecto #dataloader #absinthe

Вопрос:

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

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


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

  def data, do: Dataloader.Ecto.new(Repo, query: amp;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
  

Прокомментированная часть — это распознаватель, который работает без dataloader него и не вызывает проблемы.

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

 {
  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 и не могу найти решение. В чем может быть дело?

Ответ №1:

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

Сам dataloader по себе не знает, что извлекать из базы данных, он понимает только ассоциации. Таким образом 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(amp;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