Архитектура
АрхитектураДвижок загрузки данных

Движок загрузки данных

Gato GraphQL использует серверные компоненты для представления модели данных (а не графы или деревья). Рассмотрим, как он выполняет процесс загрузки данных для разрешения GraphQL-запроса.

Чтобы обработать данные, необходимо выровнять компоненты в типы (<FeaturedDirector> => Director, <Film> => Film, <Actor> => Actor), упорядочить их в соответствии с их появлением в иерархии компонентов (Director, затем Film, затем Actor) и обрабатывать их «итерациями», извлекая данные объектов для каждого типа в своей собственной итерации:

Обработка типов итерациями

Движок загрузки данных сервера должен реализовать следующий (псевдо-)алгоритм для загрузки данных:

Подготовка:

  1. Подготовить пустую очередь для хранения списка ID объектов, которые должны быть получены из базы данных, организованных по типу (каждая запись будет: [тип => список ID])
  2. Получить ID объекта избранного режиссёра и поместить его в очередь под типом Director

Цикл до тех пор, пока в очереди не останется записей:

  1. Получить первую запись из очереди: тип и список ID (например: Director и [2]), и удалить эту запись из очереди
  2. Используя объект TypeDataLoader типа, выполнить один запрос к базе данных для получения всех объектов этого типа с указанными ID
  3. Если тип имеет реляционные поля (например: тип Director имеет реляционное поле films типа Film), то собрать все ID из этих полей у всех объектов, полученных в текущей итерации (например: все ID в поле films у всех объектов типа Director), и поместить эти ID в очередь под соответствующим типом (например: ID [3, 8] под типом Film).

По завершении итераций все данные объектов для всех типов будут загружены:

Обработка типов итерациями

Обратите внимание, как собираются все ID для типа до того, как тип будет обработан в очереди. Если, например, добавить реляционное поле preferredActors к типу Director, эти ID будут добавлены в очередь под типом Actor и будут обработаны вместе с ID из поля actors типа Film:

Обработка типов итерациями

Однако если тип уже был обработан, а затем нам нужно загрузить больше данных этого типа, это будет новой итерацией по этому типу. Например, добавление реляционного поля preferredDirector к типу Author приведёт к тому, что тип Director будет снова добавлен в очередь:

Итерация по повторяющемуся типу

Теперь, когда все данные объектов получены, необходимо придать им форму ожидаемого ответа, отражающую GraphQL-запрос. Однако, как видно, данные не имеют требуемой древовидной структуры. Вместо этого реляционные поля содержат ID вложенного объекта, эмулируя то, как данные представлены в реляционной базе данных. Следовательно, продолжая эту аналогию, данные, полученные для каждого типа, можно представить в виде таблицы:

Таблица для типа Director:

IDnamecountryavatarfilms
2George LucasUSAgeorge-lucas.jpg[3, 8]

Таблица для типа Film:

IDtitlethumbnailactors
3The Phantom Menaceepisode-1.jpg[4, 6]
8Attack of the Clonesepisode-2.jpg[6, 7]

Таблица для типа Actor:

IDnameavatar
4Ewan McGregormcgregor.jpg
6Nathalie Portmanportman.jpg
7Hayden Christensenchristensen.jpg

Имея все данные, организованные в виде таблиц, и зная, как каждый тип связан с остальными (то есть Director ссылается на Film через поле films, Film ссылается на Actor через поле actors), GraphQL-сервер может легко преобразовать данные в ожидаемую древовидную форму:

Ответ в виде дерева

Наконец, GraphQL-сервер выводит дерево, которое имеет форму ожидаемого ответа:

{
  data: {
    featuredDirector: {
      name: "George Lucas",
      country: "USA",
      avatar: "george-lucas.jpg",
      films: [
        {
          title: "Star Wars: Episode I",
          thumbnail: "episode-1.jpg",
          actors: [
            {
              name: "Ewan McGregor",
              avatar: "mcgregor.jpg",
            },
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            }
          ]
        },
        {
          title: "Star Wars: Episode II",
          thumbnail: "episode-2.jpg",
          actors: [
            {
              name: "Natalie Portman",
              avatar: "portman.jpg",
            },
            {
              name: "Hayden Christensen",
              avatar: "christensen.jpg",
            }
          ]
        }
      ]
    }
  }
}

Анализ временной сложности решения

Проанализируем нотацию «большое O» алгоритма загрузки данных, чтобы понять, как растёт количество запросов к базе данных по мере увеличения числа входных данных, и убедиться, что данное решение является производительным.

Движок загрузки данных загружает данные в итерациях, соответствующих каждому типу. К моменту начала итерации у него уже будет список всех ID всех объектов для получения, поэтому он может выполнить один запрос для получения всех данных соответствующих объектов. Отсюда следует, что количество запросов к базе данных будет расти линейно с числом типов, задействованных в запросе. Иными словами, временная сложность составляет O(n), где n — количество типов в запросе (однако если тип итерируется более одного раза, он должен быть добавлен к n более одного раза).

Это решение очень производительно — значительно лучше экспоненциальной сложности, ожидаемой при работе с графами, или логарифмической сложности при работе с деревьями.

Реализованный PHP-код

Процесс загрузки данных происходит в функции getComponentData класса Engine из пакета Component Model.