Архитектура
АрхитектураДизайн директив

Дизайн директив

Директивы играют важную роль: они позволяют реализовать те функции, которые не поддерживаются нативно спецификацией GraphQL или самим GraphQL-сервером. Таким образом, директивы помогают восполнить функциональные пробелы, чтобы API мог удовлетворять как известным, так и неизвестным требованиям.

По этой причине директивы являются крайне важным элементом в основе GraphQL-сервера. Gato GraphQL опирается на продуманный и надёжный архитектурный дизайн директив, который делает его одновременно расширяемым и мощным.

Низкоуровневая функциональность

Как архитектурное решение, движок напрямую зависит от конвейера директив при разрешении запроса. По этой причине директивы рассматриваются как низкоуровневые компоненты с доступом к объекту, в котором хранится ответ.

В результате любая пользовательская директива обладает возможностью изменять GraphQL-ответ.

Очевидным примером использования является директива @remove, которая позволяет указать в запросе, что лучше опустить ответ поля, чем получить значение null (в спецификации существует issue, касающийся этой функции).

Эффективные вызовы директив

Директивы получают все затронутые объекты и поля вместе, за одно выполнение.

Например, обращение к Google Translate API должно выполняться как можно реже. В этом запросе оно вызывается всего один раз, передавая 10 фрагментов текста для перевода (2 поля — title и excerpt — для 5 постов):

query {
  posts(pagination:{ limit: 5 }) {
    title
    excerpt
    titleES: title @translate(from: "en", to: "es")
    excerptES: excerpt @translate(from: "en", to: "es")
  }
}

В этом запросе 3 обращения к API — по одному на каждый язык (испанский, французский и немецкий), по 10 строк в каждом, и все вызовы выполняются параллельно:

query {
  posts(pagination:{ limit: 5 }) {
    title
    excerpt
    titleES: title @translate(from: "en", to: "es")
    excerptES: excerpt @translate(from: "en", to: "es")
    titleDE: title @translate(from: "en", to: "de")
    excerptDE: excerpt @translate(from: "en", to: "de")
    titleFR: title @translate(from: "en", to: "fr")
    excerptFR: excerpt @translate(from: "en", to: "fr")
  }
}

Сигнатура функции

Вот интерфейс директивы поля. Обратите внимание на параметры, которые принимает функция resolveDirective:

public function resolveDirective(
  RelationalTypeResolverInterface $relationalTypeResolver,
  array $idFieldSet,
  FieldDataAccessProviderInterface $fieldDataAccessProvider,
  array $succeedingPipelineFieldDirectiveResolvers,
  array $idObjects,
  array $unionTypeOutputKeyIDs,
  array $previouslyResolvedIDFieldValues,
  array &$succeedingPipelineIDFieldSet,
  array &$succeedingPipelineFieldDataAccessProviders,
  array &$resolvedIDFieldValues,
  array &$messages,
  EngineIterationFeedbackStore $engineIterationFeedbackStore,
): void;

Эти параметры свидетельствуют о низкоуровневой природе директивы:

  • $idFieldSet: список ID по полям, которые должна обработать директива
  • $succeedingPipelineIDFieldSet: список ID по полям, которые должны обработать директивы на более поздних этапах конвейера
  • $resolvedIDFieldValues: объект ответа

Остальные параметры позволяют: получать доступ к переменным запроса и определять динамические переменные, передавать сообщения с пользовательскими данными между директивами, генерировать ошибки и предупреждения, выявлять и отображать устаревшие элементы, а также сохранять метрики.