Построение динамических запросов 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, и я изо всех сил пытаюсь понять, возможно ли манипулировать запросом таким образом.

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

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Что такое Apollo Client и зачем он нужен?
Что такое Apollo Client и зачем он нужен?
Apollo Client - это полнофункциональный клиент GraphQL для JavaScript-приложений, который упрощает получение, управление и обновление данных в...
30
0
17 188
3

Ответы 3

Я думаю, вы могли бы использовать для этого фрагменты! Но вам все равно придется записать 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}
`    

Быстрый вопрос: если у меня есть несколько фрагментов, длина которых мне неизвестна, могу ли я таким образом построить mainQuery? например, динамически добавлять в запрос несколько фрагментов?

Evilsanta 01.11.2018 05:33

@Evilsanta, если вы динамически определяете фрагменты и их ссылку в основном запросе, я думаю, что это возможно.

Marco Daniel 08.11.2018 08:06

Вы можете использовать фрагмент для определения общих полей и привязанные к переменной директивы @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
}

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

@ Мэтт Уилсон, вы когда-нибудь придумывали решение этой проблемы? Я в такой же ситуации. У меня есть массив идентификаторов из другой службы, и для каждого мне нужно вызвать запрос graphql и попытаться вызвать их либо как 1, либо партиями

user3067684 04.08.2020 14:49

@ Mr.7 Привет, я использую Apollo и Apollo Client и обнаружил, что этот "BatchHttpLink" - это метод. Я помещаю свое решение здесь: koala-moon.com/vue-js-apollo-set-up-with-batch-query-process‌ing И документы Apollo: apollographql.com/docs/link/links/batch-http На стороне клиента вы просто создаете новое соединение с сервером graphql (Apollo), которое ждет некоторое время перед отправкой запроса или запросов в пакете. За последние несколько месяцев он без проблем обрабатывал сотни запросов пользователей.

user3067684 05.10.2020 13:16

Спасибо @ user3067684 Я посмотрю :)

Mr.7 06.10.2020 05:02

Я думаю, у вас нет выбора, кроме использования функций 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 (из-за того, что вы создаете его динамически)

Другие вопросы по теме