Синтаксис запросов
Интерфейс GraphQL API в настоящий момент находится в разработке, в связи с чем часть возможностей языка и системы может быть недоступна.
Для наиболее эффективного взаимодействия с GraphQL API необходимо понимать структуру и синтаксис запросов. Для полного понимания всех возможностей и ограничений языка рекомендуется ознакомиться со спецификацией GraphQL. В данной же статье описаны основные компоненты запросов и примеры их применения, основанные на схеме АЛОР Брокер GraphQL API.
Структура запроса
Основные компоненты
Структура запросов к GraphQL API оперирует двумя основными сущностями: операциями и полями.
- Операция (Operation) — метод взаимодействия с данными, хранящимися в системе.
- Поле (Field) — единица данных, запрошенная у системы или указатель пути к ней.
query {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
currencyInformation {
nominal
settlement
}
}
}
Где:
query
— тип операцииinstrument
,basicInformation
,currencyInformation
и прочие — поля, указывающие путь к запрашиваемым данным
Параметры symbol
, exchange
и board
— обязательные аргументы поля instrument
, без которых запрос к нему не может быть выполнен. Подробнее об аргументах в разделе Дополнительные компоненты.
Подробнее о каждом из компонентов:
Операции
GraphQL API торговой системы АЛОР Брокер поддерживает только один тип операций — query
.
Сообщения этого типа операций обеспечивают однократное взаимодействие с сервером и используются для получения данных от системы. В качестве транспортного протокола для этого типа операций используется HTTP. По функционалу операции этого типа схожи с GET-запросами к HTTP API, но выполняются при этом с помощью POST-запросов.
Для обозначения типа операции добавьте его на корневой уровень запроса.
- Можно не указывать тип операции, если тело запроса содержит только одну операцию, в которой не используются переменные и директивы. В противном случае тип операции должен быть указан
- Для добавления аргументов (например, объявления переменных) в корень запроса операция должна иметь имя
Поля
Поля — основа запроса в GraphQL. Полями описываются как сами данные, к которым обращён запрос, так и путь до них. Для удобства можно разделить их следующим образом:
- Поле данных — поле, непосредственно указывающее на данные, которые можно запросить у системы
- Поле пути — указатель пути до поля данных согласно структуры схемы
query {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
currencyInformation {
nominal
settlement
}
}
}
В запросе выше присутствуют оба типа полей:
instrument
,basicInformation
иcurrencyInformation
— поля пути, имеющие собственные вложения и указывающие путь к данным согласно структуры схемыdescription
,shortName
и прочие — поля данных, определяющие сами данные для возвращения сервером
В схеме поля могут быть представлены как type
, object
или interface
(или их содержимое) в зависимости от роли поля в структуре данных.
Так, например, запрос выше включает в себя interface Instrument
, type BasicInformation
, type CurrencyInformation
и их содержимое.
- В теле запроса должно быть указано как минимум одно поле данных
- Все поля пути, составляющие путь к полю данных, должны быть указаны в теле запроса согласно схеме
Дополнительные компоненты
Помимо основных компонентов, характеризующих структуру запрашиваемых данных, GraphQL поддерживает также ряд дополнительных параметров, упрощающих работу с системой:
- Именованная операция — возможность присвоить операции уникальное имя. Именование операций не только помогает отличать различные операции друг от друга в системе, но и требуется для использования некоторых других возможностей языка, например, назначения переменных.
- Аргумент (Argument) — дополнительный параметр запроса, уточняющий поиск данных или ограничивающий их выдачу.
- Псевдоним (Alias) — заданное отправителем запроса имя поля, объекта или интерфейса, под которым система вернёт запрошенные данные вместо определённого системой.
- Переменная (Variable) — инструмент параметризации запроса, позволяющий указывать и передавать значения аргументов не напрямую в теле операции, а с помощью отдельного словаря переменных.
- Фрагмент (Fragment) — инструмент, дающий возможность переиспользовать поля, объекты и интерфейсы в нескольких запросах без необходимости прописывать их для каждого запроса отдельно.
- Директива (Directive) — инструмент динамического управления запросами, определяющий выполняемые в рамках операции действия и условия в соответствии с переданными данными.
В некоторых случаях аргументы могут быть обязательным параметром для выполнения запроса. Если в схеме у объекта, интерфейса или операции прописаны аргументы с восклицательным знаком !
после формата значения — этот аргумент обязателен для заполнения и без него запрос не будет выполнен.
query customQueryName($customSymbolName1: String!, $customSymbolName2: String!, $getBasicInfo: Boolean!, $getCurrencyInfo: Boolean!) {
CustomInstrument1: instrument(symbol: $customSymbolName1, exchange: "MOEX", board: "TQBR") {
basicInformation @include(if: $getBasicInfo) {
...basicInfoSelectionSet
}
currencyInformation @include(if: $getCurrencyInfo) {
...currencyInfoSelectionSet
}
}
CustomInstrument2: instrument(symbol: $customSymbolName2, exchange: "MOEX", board: "TQBR") {
basicInformation @include(if: $getBasicInfo) {
...basicInfoSelectionSet
}
currencyInformation @include(if: $getCurrencyInfo) {
...currencyInfoSelectionSet
}
}
}
fragment basicInfoSelectionSet on BasicInformation {
description
shortName
type
market
}
fragment currencyInfoSelectionSet on CurrencyInformation {
settlement
nominal
}
{
"customSymbolName1": "SBER",
"customSymbolName2": "VTBR",
"getBasicInfo": true,
"getCurrencyInfo": false
}
Где:
customQueryName
— имя операции$customSymbolName1
,$customSymbolName2
,$getBasicInfo
и$getCurrencyInfo
— переменные, чьи значения подставляются в текст запроса при его обработке системой. Значения переменных заданы в отдельном словаре переменных в формате JSONCustomInstrument1
иCustomInstrument2
— псевдонимы интерфейсаinstrument
, позволяющие в одном запросе получить данные по одинаковым полям для разных инструментовbasicInfoSelectionSet
иcurrencyInfoSelectionSet
— фрагменты, позволяющие переиспользовать входящие в объектыBasicInformation
иCurrencyInformation
строки в нескольких интерфейсах@include(if: $getBasicInfo)
и@include(if: $getCurrencyInfo)
— директивы, указывающие на добавление объектовBasicInformation
иCurrencyInformation
в зависимости от значений переменных$getBasicInfo
и$getCurrencyInfo
- Всё, что заключено в круглые скобки — аргументы задействованных объектов, интерфейсов и операций
Подробнее о каждом из компонентов:
Имя операции
Каждой операции в сообщении, передаваемом системе для обработки, можно назначить уникальное имя. Присвоение имени операции полезно, когда требуется:
- Объявить в операции переменные, которые задаются как аргументы имени операции
- Передать в одном документе несколько операций с возможностью их идентификации
Объявление переменных описано ниже, в связи с чем остановимся на идентификации нескольких операций в одном документе.
Документом в GraphQL называется тело сообщения, в котором передаётся вся полезная нагрузка, обрабатываемая сервером, за исключением значений переменных, которые передаются в отдельном словаре.
В одном документе может размещаться одновременно несколько не связанных друг с другом операций, которые могут передаваться как по одной за раз, так и все разом. Присвоение операции уникального имени позволяет идентифицировать её среди прочих и передавать только те операции, которые соответствуют текущим потребностям.
Имена операций не зависят от схемы напрямую и определяются самим пользователем. Предзаданные в схеме имена операций должны расцениваться пользователем как вызванная структурой схемы необходимость или вариант обозначения той или иной операции.
Для присвоения операции собственного имени достаточно указать его после типа операции:
query Instrument {
instrument(symbol: SBER, exchange: MOEX, board: TQBR) {
basicInformation {
...
}
}
}
Где:
Instrument
— имя операции
- Имя операции должно быть уникальным для документа
- Количество имён операций на один документ не ограничени
- Заданное имя не должно полностью повторять имена других компонентов запроса, схемы или служебных элементов GraphQL (например,
type
)
Аргументы
Аргументы позволяют указать дополнительные параметры операции или поля. В случае с полями такие дополнительные параметры позволяют уточнить или ограничить возвращаемые системой данные, тогда как для операций аргументы позволяют объявить используемые в запросе переменные. Ближайшим аналогом аргументов в GraphQL можно назвать Query-параметры запросов к HTTP API.
Для полей перечень доступных аргументов определяется схемой и заключается в круглые скобки:
instrument(symbol: String!, exchange: String!, board: String!): Instrument
Где:
Instrument
— интерфейс, поля которого будут использованы при поиске данных и составлении ответа системойinstrument
— псевдоним, которым вызывается интерфейс. Именно псевдоним выступает в качестве поля в запросеsymbol
,exchange
,board
— аргументы псевдонима для уточнения запросаString
— формат данных, которому должно соответствовать передаваемое значение
Следуя схеме, запрос с аргументами будет выглядеть следующим образом:
{
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
...
}
}
}
Формат записи аргументов в схеме не играет роли:
instruments(
includeOld: Boolean! = false
includeNonBaseBoards: Boolean! = false
first: Int
after: String
last: Int
before: String
where: InstrumentModelFilterInput
order: [InstrumentModelSortInput!]
): InstrumentsConnection
Где:
InstrumentsConnection
— тип, поля которого будут использованы при поиске данных и составлении ответа системойinstruments
— псевдоним, которым вызывается типincludeOld
,includeNonBaseBoards
,first
,after
,last
,before
,where
иorder
— аргументы псевдонима для уточнения запроса
При этом в запросе все аргументы будут так же указаны в одну строку:
{
instruments(includeOld: false, includeNonBaseBoards: false, last: 50, order: ) {
totalCount
nodes {
basicInformation {
...
}
}
}
}
Аргументы могут носить как обязательный, так и опциональный характер. Без обязательных параметров запрос не будет выполнен, так как система не получит достаточно данных для составления ответа. Опциональные же параметры позволяют уточнить работоспособный запрос или применить к нему дополнительные настройки сортировки или фильтрации.
Обязательный характер аргумента указывается в схеме с помощью восклицательного знака !
при указании формата данных ожидаемого значения. Если его нет — аргумент носит опциональный характер.
Так, например, в схеме выше аргументы includeOld
и includeNonBaseBoards
носят обязательный для заполнения характер, когда такие аргументы, как before
и after
могут быть проигнорированы.
Некоторым аргументам в схеме может быть задано значение по умолчанию, указываемое после знака равенства =
. Такие аргументы всё ещё носят обязательный характер, но их не обязательно указывать в запросе — в этом случае будет использовано предзаданное значение.
Например, аргумент includeOld: Boolean! = false
носит обязательный характер, но при этом имеет значение по умолчанию. Если предзаданное значение соответствует задаче, этот аргумент можно опустить.
- Имя аргумента должно быть указано в запросе в полном соответствии с написанием в схеме.
Before
иbefore
— разные аргументы - Аргументы присваиваются только полям и операциям. Псевдонимы не обладают собственными аргументами
- Аргументы с восклицательным знаком
!
носят обязательный характер и для выполнения запроса для них должно быть указано значение - Аргументу может быть задано значение по умолчанию, позволяющее не прописывать аргумент в запросе, если предзаданное значение соответствует задаче
Псевдонимы
Псевдонимы позволяют указать собственное имя для поля, под которым система вернёт запрошенные данные. Это может быть полезно, если одно и то же поле несколько раз встречается в одном запросе или когда клиентское приложение, отправившее запрос, ожидает получить в ответе данные под определёнными именами.
Псевдонимы не зависят от схемы напрямую и определяются самим пользователем. Предзаданные в схеме псевдонимы должны расцениваться пользователем как обычные поля.
Допустим, существует задача получить в рамках одной операции данные по одинаковым полям для инструментов SBER
и VTBR
одновременно. Если просто продублировать объект instrument
:
query {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
instrument(symbol: "VTBR", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
}
При получении данных произойдёт ошибка слияния полученных данных. Причина — одинаковое имя instrument
для обоих тикеров.
В таком случае можно присвоить каждому из полей instrument
псевдоним, сделав их уникальными:
query {
Instrument1: instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
Instrument2: instrument(symbol: "VTBR", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
}
Где:
instrument
— вызываемое полеInstrument1
иInstrument2
— заданные пользователем псевдонимы для разделения данных от поляinstrument
Таким образом, после присвоения объекту с тикером SBER
псевдоним instrument1
, а объекту VTBR
псевдоним instrument2
, были созданы объекты с уникальными именами, чьи данные не конфликтовали при слиянии.
- Количество псевдонимов в одном запросе не ограничено
- Каждый псевдоним должен быть уникальным
- Псевдоним не должен полностью повторять имена компонентов, уже фигурирующие в схеме, либо служебные имена компонентов GraphQL (например,
type
)
Фрагменты
Некоторые наборы полей могут использоваться в одном документе несколько раз, что вызывает увеличение сетевой нагрузки от увеличения передаваемых пакетов данных, а также затрудняет чтение и изменение документа. Фрагменты позволяют оптимизировать тело документа, заменив повторение всего набора полей одним указателем на вынесенный за границы операции набор.
Чтобы объявить новый фрагмент, необходимо вынести его за пределы любых операций в документе, объявить с помощью оператора fragment
, задать имя, происхождение и указать содержимое.
Например, чтобы создать фрагмент из входящего в Instrument
поля basicInformation
и некоторых входящих в него полей, в тело запроса должен быть добавлен следующий объект:
fragment basicInfoFragment on Instrument {
basicInformation {
description
shortName
type
market
}
}
Где:
fragment
— оператор, объявляющий объект в документе как фрагментbasicInfoFragment
— уникальное имя фрагментаInstrument
— ссылающееся на интерфейс поле, в котором будет размещаться фрагментbasicInformation
,description
,shortName
,type
,market
— поля, из которых состоит фрагмент
Чтобы использовать созданный фрагмент в операции, добавьте в тело операции имя фрагмента с префиксом ...
:
query {
instrument1: instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
...basicInfoFragment
}
}
Таким образом, документ с повторяющимися полями вроде этого (681 байт UTF-8):
query {
instrument1: instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
instrument2: instrument(symbol: "VTBR", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
instrument3: instrument(symbol: "TCSG", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
instrument4: instrument(symbol: "SVCB", exchange: "MOEX", board: "TQBR") {
basicInformation {
description
shortName
type
market
}
}
}
С помощью фрагмента может быть преобразован так (581 байт UTF-8):
query {
instrument1: instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
...basicInfoFragment
}
instrument2: instrument(symbol: "LKOH", exchange: "MOEX", board: "TQBR") {
...basicInfoFragment
}
instrument3: instrument(symbol: "TCSG", exchange: "MOEX", board: "TQBR") {
...basicInfoFragment
}
instrument4: instrument(symbol: "SVCB", exchange: "MOEX", board: "TQBR") {
...basicInfoFragment
}
}
fragment basicInfoFragment on Instrument {
basicInformation {
description
shortName
type
market
}
}
При объявлении фрагмента необходимо структурно обозначить последовательность подставляемых в запрос полей. Так, если по иерархии изъятые поля являются вложением поля instrument
, при объявлении фрагмента целью для него должно быть объявлено именно это поле.
Кроме того, GraphQL поддерживает создание составных фрагментов, в которых один фрагмент ссылается на другой. Например:
query GetBasicInfo {
instrument(symbol: "SBER", board: "TQBR", exchange: "MOEX") {
...getBasicInfoFragment
}
}
fragment getBasicInfoFragment on Instrument {
basicInformation {
symbol
...getDescriptionFragment
...getMarketFragment
}
}
fragment getDescriptionFragment on BasicInformation {
shortName
description
}
fragment getMarketFragment on BasicInformation {
market
type
}
Где:
getBasicInfoFragment
— фрагмент, использующийся в теле операции и содержащий в себе более низкоуровневые фрагментыgetDescriptionFragment
иgetMarketFragment
— низкоуровневые фрагменты, входящие вgetBasicInfoFragment
и содержащие поля данныхsymbol
— поле данных, размещённое в фрагментеgetBasicInfoFragment
наравне с низкоуровневыми фрагментами
- Количество фрагментов в одном документе не ограничено
- Каждый фрагмент должен обладать уникальным именем
- Имя фрагмента не должно полностью повторять имена компонентов, уже фигурирующие в схеме, либо служебные имена компонентов GraphQL (например,
type
илиon
) - Фрагмент должен быть составлен и размещён в теле операции в соответствии с определённой структорой схемы иерархией
- Фрагмент не должен использоваться в объектах, которым не соответствует его содержимое
- Фрагмент должен содержать в себе тип, объект или интерфейс. Нельзя создавать фрагменты, состоящие только из полей данных
- Фрагмент может содержать в себе аргументы входящих в него полей, но ему нельзя присвоить собственные аргументы
- В фрагментах можно использовать переменные, но переменными нельзя задавать имя фрагмента
- Объявленный в документе фрагмент должен быть использован как минимум в одной операции
- Составные фрагменты не должны образовывать цикл, рекурсивно ссылаясь друг на друга
Переменные
Статического указания данных в запросе достаточно, когда речь идёт о тестовых взаимодействиях или одиночных запросах. В практическом применении становится сложнее изменять значения во всей операции каждый раз, когда нужно передать новые вводные.
Вместо статически указанных данных можно использовать переменные, значения которых будут подставляться в запрос автоматически.
Чтобы задать переменную:
- Объявите переменную с требуемым форматом данных для передаваемого значения в аргументах операции
- Разместите переменную в теле операции
- Объявите значение переменной в словаре переменных
В словаре переменных указывается только имя переменной. В имени и теле операции это имя должно сопровождаться префиксом $
Чтобы объявить переменную в операции, присвойте ей уникальное имя и в аргументах к нему объявите имя добавляемой переменной и формат данных, которому должно соответствовать значение:
query QueryWithVariable($symbolvar: String) {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
...
}
}
Где:
$symbolvar: String
— переменнаяsymbolvar
с форматом данныхString
В качестве возможных форматов данных GraphQL принимает следующие варианты:
- String
- Int
- Float
- Boolean
Как и предзаданным аргументам в схеме, объявленным в имени операции переменным можно придать обязательный характер — операция не будет выполнена, если переменной не присвоено значение, соответствующее указанному формату данных:
query QueryWithVariable($symbolvar: String!) {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
...
}
}
Где:
$symbolvar: String!
— переменнаяsymbolvar
с форматом данныхString
и указателем!
, придающим переменной обязательный характер
Если переменной заменяется значение аргумента из схемы, свойства аргумента должны быть полностью унаследованы этой переменной. Например, если аргумент ожидает String!
, переменной нельзя задать String
.
Чтобы разместить переменную в теле операции, добавьте её имя с префиксом вместо статического значения:
query QueryWithVariable($symbolvar: String!) {
instrument(symbol: $symbolvar, exchange: "MOEX", board: "TQBR") {
...
}
}
Где:
$symbolvar: String!
— объявление переменной с форматом данных в аргументах операцииsymbol: $symbolvar
— размещение переменной в качестве значения для аргументаsymbol
В качестве словаря переменных выступает отдельная сущность, формат которой зависит от выбранного транспортного протокола (чаще всего JSON). Чтобы объявить переменную в словаре, добавьте её имя новой строкой и укажите её значение:
{
"symbolvar": "SBER"
}
- Количество переменных в одном документе не ограничено
- Каждая переменная должна обладать уникальным именем
- Имя переменной не должно полностью повторять имена компонентов, уже фигурирующие в схеме, либо служебные имена компонентов GraphQL
- Если переменная используется для передачи значения аргумента, она должна наследовать все свойства ожидаемого значения аргумента из схемы
- Объявленная в словаре переменная должна быть хоть раз использована в теле операции
- Переменные можно использовать внутри фрагментов, но нельзя использовать для передачи имени фрагмента
- Переменные, размещённые в неиспользуемых фрагментах, считаются неиспользованными
- В переменных можно передавать целые объекты, но их не стоит рассматривать как замену фрагментам
Директивы
Для решения некоторых задач может потребоваться временно изменить набор полей в уже сформированном запросе. Такие задачи можно решить, создав несколько отдельных операций или введя в тело операции директивы.
Директивы позволяют создать альтернативные сценарии исполнения одной и той же операции без необходимости вручную изменять всё тело операции. В настоящий момент GraphQL поддерживает два типа директив:
@include
— включить содержимое директивы в запрос@skip
— исключить содержимое директивы из запроса
Чтобы объявить директиву, добавьте её ключ вместе с условием выполнения в качестве аргумента в тело операции:
query {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation @include(if: $getBasicInfo) {
...
}
currencyInformation @skip(if: $getCurrencyInfo) {
...
}
}
}
Где:
@include
и@skip
— ключи директивif: $getBasicInfo
иif: $getCurrencyInfo
— условия выполнения директив
В качестве условия выполнения директивы используются переменные, которые должны быть объявлены в соответствии с применяемым к ним правилам. Правильно составленное тело операции будет выглядеть так:
query QueryWithDirectives($getBasicInfo: Boolean!, $getCurrencyInfo: Boolean!) {
instrument(symbol: "SBER", exchange: "MOEX", board: "TQBR") {
basicInformation @include(if: $getBasicInfo) {
...
}
currencyInformation @skip(if: $getCurrencyInfo) {
...
}
}
}
{
"getBasicInfo": true,
"getCurrencyInfo": false
}
В представленном выше содержимое полей basicInformation
и currencyInformation
будет добавлено в ответ, так как для @include
условие выполняется (значение управляющей переменной true
), а для @skip
— нет (значение переменной false
).
- Количество директив в одном документе не ограничено
- Для управления директивами используются переменные, которые подчиняются собственным требованиям в полной мере
- Ключи директив зависят от схемы и не могут быть самостоятельно объявлены пользователем
- Директивы можно размещать в фрагментах, равно как фрагменты можно указывать как содержимое директив
- Нельзя разместить несколько директив для одного и того же поля, равно как нельзя задать несколько аргументов для одной директивы. Варианты написания
basicInformation @include(if: $getBasicInfo) @include(if: $getCurrencyInfo)
илиbasicInformation @include(if: $getBasicInfo, $getCurrencyInfo)
будут восприняты как ошибка синтаксиса - Одна переменная может управлять несколькими директивами одновременно
Что дальше?
- Ознакомьтесь детальнее со спецификацией GraphQL, официальной документацией разработчиков и ответами на часто задаваемые вопросое
- Узнайте о дополнительных возможностях GraphQL API, доступных при взаимодействии с АЛОР Брокер API: получение массивов данных, постраничный вывод, фильтрация и сортировка данных
- Опробуйте полученные знания в веб-песочнице боевого или тестового контура