Arquitetura
ArquiteturaDesign de directives

Design de directives

As directives desempenham um papel importante: permitem implementar as funcionalidades que não são suportadas nativamente pela especificação GraphQL ou pelo próprio servidor GraphQL. As directives podem, assim, preencher essa lacuna funcional, para que a API consiga atender aos seus requisitos, sejam eles conhecidos ou desconhecidos.

Por esse motivo, as directives são um elemento extremamente importante nas fundações do servidor GraphQL. O Gato GraphQL se baseia em um design arquitetural sólido e robusto para as directives, o que o torna ao mesmo tempo extensível e poderoso.

Funcionalidade de baixo nível

Como decisão de design, o motor depende diretamente do pipeline de directives para resolver a query. Por esse motivo, as directives são tratadas como componentes de baixo nível, com acesso ao objeto onde a resposta é armazenada.

Como resultado, qualquer directive personalizada tem o poder de modificar a resposta GraphQL.

Um caso de uso evidente é a directive @remove, que permite indicar na query se preferimos omitir a resposta de um campo em vez de receber um valor null (existe uma issue na especificação relacionada a essa funcionalidade).

Chamadas eficientes às directives

As directives recebem todos os seus objetos e campos afetados juntos, em uma única execução.

Por exemplo, a chamada à API do Google Translate deve ser feita o menor número de vezes possível. Nesta query, ela é chamada apenas uma vez, contendo 10 trechos de texto para traduzir (2 campos, title e excerpt, para 5 posts):

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

Nesta query há 3 chamadas à API, uma para cada idioma (espanhol, francês e alemão), 10 strings cada, todas as chamadas são concorrentes:

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")
  }
}

Assinatura da função

Esta é a interface de directive de campo. Observe os parâmetros que a função resolveDirective recebe:

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;

Esses parâmetros evidenciam a natureza de baixo nível da directive:

  • $idFieldSet: a lista de IDs por campo a serem processados pela directive
  • $succeedingPipelineIDFieldSet: a lista de IDs por campo a serem processados pelas directives em uma etapa posterior do pipeline
  • $resolvedIDFieldValues: o objeto de resposta

Os demais parâmetros permitem: acessar as variáveis da query e definir variáveis dinâmicas, passar mensagens com dados personalizados entre as directives, gerar erros e avisos, identificar e exibir depreciações, e armazenar métricas.