Построение динамических запросов GraphQL

У меня есть сервер GraphQL, который может обслуживать данные таймсерий для указанного источника (например, данные датчика). Пример запроса для получения данных для датчика может быть таким:

query fetchData {
    timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
}

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

query fetchData {
    series1: timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
    series2: timeseriesData(sourceId: "source2") {
      data {
        time
        value
      }
    }
}

Большинство руководств по GraphQL, похоже, сосредоточены на статических запросах (например, когда изменяется только переменная, но не фактическая форма запроса), но в моем случае мне нужно, чтобы запрос был динамический (один запрос timeseriesData для каждого из выбранных мной идентификаторов).

У меня есть следующие ограничения:

  1. Изменение схемы сервера не является вариантом (поэтому, например, я не могу передать массив идентификаторов в преобразователь)
  2. Мой запрос указан с использованием строки шаблона, например. gql` ... `
  3. Я не хочу вручную создавать запрос в виде строки, потому что это похоже на рецепт катастрофы и будет означать, что я потеряю все преимущества инструментов (например, автозаполнение, подсветка синтаксиса, линтинг)

Стек, который я использую:

  • Клиент Apollo (в частности, apollo-angular)
  • Угловой
  • Машинопись
  • graphql-tag (для определения запросов)

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

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

Какие существуют методы для создания подобного динамического запроса, когда форма запроса неизвестна заранее?


person Matt Wilson    schedule 13.07.2018    source источник


Ответы (3)


Вы можете использовать фрагмент для определения общих полей и привязанные к переменной директивы @include(if: Boolean) и @skip(if: Boolean) для этого фрагмента, чтобы получить динамические поля, известные во время выполнения.

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

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

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

И в переменных:

{
  "episode": "JEDI",
  "withFriends": false
}

Конечно, вы можете отправлять именованные запросы, как во втором примере. Клиенты будут автоматически группировать запросы.

person snnsnn    schedule 23.02.2019
comment
@ Мэтт Уилсон, вы когда-нибудь придумывали решение этой проблемы? Я в такой же ситуации. У меня есть массив идентификаторов из другой службы, и для каждого мне нужно вызвать запрос graphql и попытаться вызвать их либо как 1, либо партиями - person user3067684; 04.08.2020
comment
@ Mr.7 Привет, я использую Apollo и Apollo Client и нашел этот метод BatchHttpLink. Я поместил свое решение здесь: koala-moon .com / vue-js-apollo-set-up-with-batch-query-processing И документы Apollo: https://www.apollographql.com/docs/link/links/batch.-http/ На стороне клиента вы просто создаете новое соединение с (Apollo) сервером graphql, которое ждет длительное время времени до отправки запроса или запросов в пакете. За последние несколько месяцев он без проблем обрабатывал сотни запросов пользователей. - person user3067684; 05.10.2020
comment
Спасибо @ user3067684 Я посмотрю :) - person Mr.7; 06.10.2020

Я думаю, вы могли бы использовать для этого фрагменты! Но вы все равно должны написать 2 "queries" в этом случае fragments.

Сначала давайте создадим fragment для каждого timeSeries, пожалуйста, проверьте свой тип запроса timeSeries, я буду называть его timeseriesDataQuery

const series1Q = gql`
  fragment series1 on timeseriesDataQuery {
    series1: timeseriesData(sourceId: "source1") {
      data {
        time
        value
      }
    }
  }
}

const series2Q = gql`
  fragment series2 on timeseriesDataQuery {
    series2: timeseriesData(sourceId: "source2") {
      data {
        time
        value
      }
    }
  }
}

А потом просто сшиваем их в запросе:

export const mainQuery = gql`
    query fetchData {
      ...series1 
      ...series2
    }
    ${series1Q}
    ${series2Q}
`    
person Marco Daniel    schedule 13.07.2018
comment
Быстрый вопрос: если у меня есть несколько фрагментов, длина которых мне неизвестна, могу ли я таким образом построить mainQuery? например, динамически добавлять в запрос несколько фрагментов? - person Evilsanta; 01.11.2018
comment
@Evilsanta, если вы динамически определяете фрагменты и их ссылку в основном запросе, я думаю, что это возможно. - person Marco Daniel; 08.11.2018

Я думаю, у вас нет выбора, кроме использования функций String, когда пользователь выбирает датчики динамически, и даже вы не знаете датчики во время разработки (не во время выполнения).

const mainQuery = gql
  `query fetchData($sourceId: String!) {
    timeseriesData(sourceId: $sourceId) {
      data {
        time
        value
      }
    }
  }`;

const mainQueryStr = mainQuery.loc.source.body;

mainQueryStr - это строковое значение вашего запроса (для обработки динамичности вашей проблемы). Затем выполните цикл по датчикам и замените $sourceId идентификатором каждого датчика.

// You have to remove the query wrapper first
// Then replace sensor id
const sensorsQueries = sensors.map(sid => mainQueryStr
  .split(`\n`)
  .slice(1, 7)
  .replace(`$sourceId`, sid)
)

Затем вам следует присоединиться к sensorQueries и сделать новый запрос GraphQL.

const finalQuery = gql`
  query fetchData {
    ${sensorsQueries.join(`\n`)}`
  };

В этом случае вы можете использовать такие преимущества инструментов, как автозаполнение, подсветка синтаксиса и ... для запроса mainQuery, а не finalQuery (потому что вы создаете его динамически)

person Alireza Hariri    schedule 16.03.2020