Протокол GraphQL#
Введение#
Компонент DataSpace Core продукта DataSpace предоставляет потребителю возможность вызывать основные функции по протоколу GraphQL. При этом схема GraphQL строится на основе модели данных предметной области потребителя.
Расшифровку основных понятий см. в документе "Термины и определения".
Настройка#
Взаимодействие с серверной частью DataSpace осуществляется через точку доступа со следующим URL-адресом: {серверURL}/graphql.
Параметр, включающий endpoint GraphQL — dataspace.endpoint.graphql.enabled=true.
Параметр, включающий браузерный редактор GraphQL — dataspace.endpoint.graphiql.enabled=true.
Примечание
Для настройки работы endpoint /graphiql через Ingress необходимо также определить параметры:
graphiql.endpoint.graphql=/<path>/graphql graphiql.basePath=/<path> graphiql.endpoint.subscriptions=/<path>/subscriptionsВ данных параметрах
<path>— путь к сервису, согласно правилам на Ingress. Это важно для выполнения запросов с формы /graphiql.

Для изменения endpoint можно задать параметр graphql.url=....
Также доступны дополнительные настройки, влияющие на итоговый вид GraphQL-схемы:
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement— определяет расположение полей для вычислимых выражений. Тип — перечисление. Значения:ON_EACH_TYPE — поля вида
_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}на каждом интерфейсе/типе, соответствующем классу модели.ON_SEPARATE_TYPE (значение по умолчанию) — поле вида
_calc: _Calculationна каждом интерфейсе/типе, соответствующем классу модели.
dataspace.endpoint.graphql.schema.settings.generate-elems-for-selection— определяет, генерировать ли элементы для выборки свойств на основе поиска сущности. Тип — логическое значение. Значение по умолчанию — "false".dataspace.endpoint.graphql.schema.settings.generate-str-expr-variable-definition-directive— определяет, генерировать ли директиву@strExprдля определений переменных. Тип — логическое значение. Значение по умолчанию — "".dataspace.endpoint.graphql.schema.settings.generate-str-expr-field-directive— определяет, генерировать ли директиву@strExprдля полей. Тип — логическое значение. Значение по умолчанию — "true".dataspace.endpoint.graphql.schema.settings.generate-str-expr-field— определяет, генерировать ли полеstrExpr. Тип — логическое значение. Значение по умолчанию — "false".
Примечание
Настройка
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placementдобавлена в связи с введением новой стратегии генерации схемы в части вычислимых выражений (ON_SEPARATE_TYPE), позволяющей сократить общий размер GraphQL-схемы за счет определения полей для вычисления примитивных выражений разных типов в отдельный тип (_Calculation).Настройка
dataspace.endpoint.graphql.schema.settings.generate-elems-for-selectionдобавлена для того, чтобы позволить отключить генерацию элементов, необходимых для функциональности выборки набора свойств.Следующие настройки появились в связи с тем, что не все инструменты поддерживают спецификацию GraphQL от октября 2021 года (например, директивы для определений переменных появились только в спецификации от октября 2021 года):
dataspace.endpoint.graphql.schema.settings.generate-str-expr-variable-definition-directive;
dataspace.endpoint.graphql.schema.settings.generate-str-expr-field-directive;
dataspace.endpoint.graphql.schema.settings.generate-str-expr-field.Данные настройки позволяют отключить неподдерживаемые элементы и включить альтернативные.
Элементы схемы#
Примитивные типы#
Примитивные типы модели отображаются в следующие скалярные типы схемы:
Примитивный тип модели |
Скалярный тип схемы |
Формат |
Пример значения |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Дополнительные скалярные типы:
Скалярный тип схемы |
Формат |
Пример значения |
|---|---|---|
|
|
В случае, если в модели используются коллекции примитивов, то для них дополнительно инициализируются типы объектов
_${наименование скалярного типа без префикса '_'}Collection со следующими полями:
elems: [${наименование скалярного типа}!]!: элементы коллекции;count: Int!: количество элементов в коллекции.
Пример — коллекция символов и коллекция дат:
type _CharCollection {
elems: [Char!]!
count: Int!
}
type _DateCollection {
elems: [_Date!]!
count: Int!
}
Технические элементы#
Для поддержки работы основных функций на схеме введены различные технические элементы:
Директива
mergeReqSpec(спецификация запроса для слияния) для встроенных фрагментов с аргументомcond: String!— условие поиска в грамматике строковых выражений.При заданной настройке
dataspace.endpoint.graphql.schema.settings.generate-str-expr-variable-definition-directive=trueилиdataspace.endpoint.graphql.schema.settings.generate-str-expr-field-directive=trueдирективаstrExpr(элемент строкового выражения) для пометки переменных, используемых только в строковых выражениях:с аргументами:
char: Char— символ;string: String— строка;byte: Byte— целое число (1 байт);short: Short— целое число (2 байта);int: Int— целое число (4 байта);long: Long— целое число (8 байт);float: _Float4— вещественное число (4 байта);double: Float— вещественное число (8 байт);bigDecimal: BigDecimal— большое десятичное число;date: _Date— дата;dateTime: _DateTime— дата и время;offsetDateTime: _OffsetDateTime— дата и время со смещением;time: _Time— время;boolean: Boolean— логическое значение;byteArray: _ByteArray— массив байтов;chars: [Char!]— символы;strings: [String!]— строки;bytes: [Byte!]— целые числа (1 байт);shorts: [Short!]— целые числа (2 байта);ints: [Int!]— целые числа (4 байта);longs: [Long!]— целые числа (8 байт);floats: [_Float4!]— вещественные числа (4 байта);doubles: [Float!]— вещественные числа (8 байт);bigDecimals: [BigDecimal!]— большие десятичные числа;dates: [_Date!]— даты;dateTimes: [_DateTime!]— даты и времена;offsetDateTimes: [_OffsetDateTime!]— даты и времена со смещением;times: [_Time!]— времена;booleans: [Boolean!]— логические значения;byteArrays: [_ByteArray!]— массивы байтов;
повторяемая при заданной настройке
dataspace.endpoint.graphql.schema.settings.str-expr-directive-repeatable=true.
Интерфейс
_Entity(сущность) с полемid: ID!— идентификатор сущности.Перечисление
_SortOrder(порядок сортировки) с двумя допустимыми значениямиASC(по возрастанию) иDESC(по убыванию).Входной тип
_SortCriterionSpecification(спецификация критерия сортировки) с полями:crit: String!— критерий сортировки в грамматике строковых выражений;order: _SortOrder! = ASC— порядок сортировки;nullsLast: Boolean— признак следования null-значений в конце.
Тип объекта
_MergedEntitiesCollection(коллекция слитых сущностей) с полями:elems: [_Entity!]!— элементы коллекции;count: Int!— количество элементов в коллекции.
Входной тип
_SingleReferenceInput(входные данные внешней ссылки на агрегат) с полемentityId: String!— идентификатор сущности.Входной тип
_SingleReferenceSetInput(входные данные коллекции внешних ссылок на агрегаты) с полями:add: [_SingleReferenceInput]— список ссылок для добавления;remove: [_SingleReferenceInput]— список ссылок для удаления;clear: Boolean— признак очистки коллекции.
Входной тип
_DoubleReferenceInput(входные данные внешней ссылки на сущность) с полями:entityId: String!— идентификатор сущности;rootEntityId: String!— идентификатор агрегата.
Входной тип
_DoubleReferenceSetInput(входные данные коллекции внешних ссылок на сущности) с полями:add: [_DoubleReferenceInput]— список ссылок для добавления;remove: [_DoubleReferenceInput]— список ссылок для удаления;clear: Boolean— признак очистки коллекции.
Входной тип
_StatusInput(входные данные статуса) с полями:code: String!— код статуса;reason: String— причина изменения статуса.
Входной тип
_TryLockInput(входные данные установки прикладной блокировки) с полями:id: ID!— идентификатор сущности;token: String— токен блокировки;timeout: Long!— тайм-аут блокировки:reason: String— причина блокировки.
Входной тип
_UnlockInput(входные данные снятия прикладной блокировки) с полями:id: ID!— идентификатор сущности;token: String!— токен блокировки.
Тип объекта
LockOutput(вывод блокировки) с полями:token: String— токен блокировки;result: Boolean— результат (успех блокировки);failReason: String— причина неудачи;timeoutEndTime: Long— время окончания тайм-аута.
Интерфейс
_Reference(ссылка) с полемentityId: String— идентификатор сущности.При заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_SEPARATE_TYPEтип объекта_Calculation(вычисление) с полями (expr— выражение в терминах грамматики строковых выражений):char(expr: String!): Char— символ;string(expr: String!): String— строка;byte(expr: String!): Byte— целое число (1 байт);short(expr: String!): Short— целое число (2 байта);int(expr: String!): Int— целое число (4 байта);long(expr: String!): Long— целое число (8 байт);float(expr: String!): _Float4— вещественное число (4 байта);double(expr: String!): Float— вещественное число (8 байт);bigDecimal(expr: String!): BigDecimal— большое десятичное число;date(expr: String!): _Date— дата;dateTime(expr: String!): _DateTime— дата и время;offsetDateTime(expr: String!): _OffsetDateTime— дата и время со смещением;time(expr: String!): _Time— время;boolean(expr: String!): Boolean— логическое значение;byteArray(expr: String!): _ByteArray— массив байтов.
Описания элементов на схеме:
interface _Entity {
id: ID!
}
enum _SortOrder {
ASC
DESC
}
input _SortCriterionSpecification {
crit: String!
order: _SortOrder! = ASC
nullsLast: Boolean
}
type _MergedEntitiesCollection {
elems: [_Entity!]!
count: Int!
}
input _SingleReferenceInput {
entityId: String!
}
input _SingleReferenceSetInput {
add: [_SingleReferenceInput]
remove: [_SingleReferenceInput]
clear: Boolean
}
input _DoubleReferenceInput {
entityId: String!
rootEntityId: String!
}
input _DoubleReferenceSetInput {
add: [_DoubleReferenceInput]
remove: [_DoubleReferenceInput]
clear: Boolean
}
input _StatusInput {
code: String!
reason: String
}
input _TryLockInput {
id: ID!
token: String
timeout: Long!
reason: String
}
input _UnlockInput {
id: ID!
token: String!
}
type LockOutput {
token: String
result: Boolean
failReason: String
timeoutEndTime: Long
}
interface _Reference {
entityId: String
}
type _Calculation {
char(expr: String!): Char
string(expr: String!): String
byte(expr: String!): Byte
short(expr: String!): Short
int(expr: String!): Int
long(expr: String!): Long
float(expr: String!): _Float4
double(expr: String!): Float
bigDecimal(expr: String!): BigDecimal
date(expr: String!): _Date
dateTime(expr: String!): _DateTime
offsetDateTime(expr: String!): _OffsetDateTime
time(expr: String!): _Time
boolean(expr: String!): Boolean
byteArray(expr: String!): _ByteArray
}
Типы перечисления#
Типы перечислений модели отображаются в типы перечислений схемы _EN_${наименование типа перечисления модели} с переносом допустимых значений без изменений.
Пример — атрибут:
enum _EN_Attribute {
SYSTEM
READ_ONLY
HIDDEN
}
В случае, если в модели используются коллекции перечислений, то для них дополнительно инициализируются типы объектов _ENC_${наименование типа перечисления модели} со следующими полями:
elems: [${тип объекта перечисления}!]!: элементы коллекции;count: Int!: количество элементов в коллекции.
Пример — коллекция атрибутов:
type _ENC_Attribute {
elems: [_EN_Attribute!]!
count: Int!
}
Классы модели#
В протоколе GraphQL нет поддержки наследования типов объектов. Поэтому для поддержки наследования классов в модели на схеме GraphQL для каждого класса модели выполняется следующее:
Создается интерфейс
${наименование класса модели}с полями:id: ID!: идентификатор сущности;aggVersion: Long!: версия агрегата сущности;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_EACH_TYPEполя для вычислимых свойств, имеющих вид_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_SEPARATE_TYPEполе_calc: _Calculationдля вычислимых свойств;поля для каждого свойства класса модели, включая свойства класса-предка.
Создается тип объекта
_E_${наименование класса модели}, реализующий:интерфейс
_Entity;интерфейс соответствующий классу модели;
интерфейсы, соответствующие всем классам-предкам модели.
Данный тип объекта с полями:
id: ID!— идентификатор сущности;aggVersion: Long!— версия агрегата сущности;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_EACH_TYPEполя для вычислимых свойств, имеющих вид_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_SEPARATE_TYPEполе_calc: _Calculationдля вычислимых свойств;поля для каждого свойства класса модели включая свойства класса-предка.
Создается тип для коллекции сущностей
_EC_${наименование класса модели}с полями:elems: [${интерфейс класса модели}!]!— элементы коллекции;count: Int!— количество элементов в коллекции.
Если имеются внешние ссылки на данный класс модели, то создается тип для внешней ссылки
_G_${наименование класса модели}Referenceс полями:entityId: String— идентификатор сущности;rootEntityId: String— идентификатор агрегата сущности (есть только в случае, если класс модели не является агрегатом);entity: ${наименование класса модели}— ссылка на сущность для запроса данных в текущем шарде.
При заданной настройке
dataspace.endpoint.graphql.schema.settings.generate-elems-for-selection=true:Создается тип для набора свойств сущности
_SE_${наименование класса модели}с полями:id: ID!— идентификатор сущности;aggVersion: Long!— версия агрегата сущности;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_EACH_TYPEполя для вычислимых свойств, имеющих вид_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений;при заданной настройке
dataspace.endpoint.graphql.schema.settings.calc-expr-fields-placement=ON_SEPARATE_TYPEполе_calc: _Calculationдля вычислимых свойств;поля для примитивных свойства класса модели включая свойства класса-предка.
Создается тип коллекции наборов свойств
_SEC_${наименование класса модели}со следующими полями:elems: [_SE_${наименование класса модели}!]!— элементы коллекции;count: Int!— количество элементов в коллекции.
Создается тип для ссылки на сущность
_R_${наименование класса модели}:реализующий интерфейс
_Reference;с полями:
entityId: String— идентификатор сущности;entity: ${интерфейс класса модели}— сущность.
Создается тип для результата мультипоиска
_ECM_${наименование класса модели}с полями:elems: [${интерфейс класса модели}!]!— элементы коллекции;count: Int!— количество элементов в коллекции;ctx: String— контекст мультипоиска, сериализованный в строку. Пример структуры контекста можно найти в разделе "Использование мультипоиска" документа "Руководство прикладного разработчика".
В зависимости от типа свойства соответствующее поле имеет определенный тип и аргументы:
примитив/перечисление — соответствующий скалярный тип/тип перечисления и не имеет аргументов;
коллекция примитивов/перечислений — соответствующий примитиву/перечислению тип объекта коллекции и аргументы:
cond: String— условие фильтрации в грамматике строковых выражений;limit: Int— ограничение на количество элементов;offset: Int— смещение;sort: [_SortCriterionSpecification!]— сортировка;
ссылка — соответствующий классу модели интерфейс и аргумент
alias: String— псевдоним;коллекция ссылок — соответствующий классу модели тип объекта коллекции и аргументы:
elemAlias: String— псевдоним элемента;cond: String— условие фильтрации в грамматике строковых выражений;limit: Int— ограничение на количество элементов;offset: Int— смещение;sort: [_SortCriterionSpecification!]— сортировка.
Данные типы преимущественно используются для запроса данных у сервиса.
Помимо этого для поддержки мутаций:
Создается входной тип для создания сущности
_Create${наименование класса модели}Inputс полями:поля для каждого свойства класса модели, включая свойства класса-предка (за исключением свойств, являющихся mappedBy-ссылками/коллекциями ссылок);
поля для каждого наблюдателя статуса класса модели
statusFor${наименование наблюдателя}: _StatusInput.
Создается входной тип для обновления сущности
_Update${наименование класса модели}Inputс полями:id: ID!— идентификатор сущности;поля для каждого свойства класса модели, включая свойства класса-предка, за исключением:
свойств, являющихся mappedBy-ссылками/коллекциями ссылок;
свойств, имеющих признак
parent=true.
Для типов сущностей, поддерживающих
compare, создается входной тип_Compate${наименование класса модели}Inputс полями участникамиcompare.Для типов сущностей, поддерживающих
inc, создается входной тип_Inc${наименование класса модели}Inputс полями участникамиinc. Тип поля зависит от типа свойства модели. Возможны следующие типы:_IncIntValueInput: для типа свойстваInteger;_IncLongValueInput: для типа свойстваLong;_IncFloatValueInput: для типа свойстваFloat;_IncDoubleValueInput: для типа свойстваDouble;_IncBigDecimalValueInput: для типа свойстваBigDecimal.
Типы
_Inc${тип поля inc}ValueInputвключат два атрибута:value: обязательный атрибут значения для суммирования;fail: опциональный атрибут условия проверки вычисленного значения поля. Тип значения_Inc${тип поля inc}ValueFailInputсодержит обязательные атрибуты:operator: логический оператор сравнения_IncFailOperator:lt— меньше (less than);le— меньше или равно (less or equal);gt— больше (greater);ge— больше или равно (greater or equal).
value: значение, с которым сравнивается вычисленное значение.
Для типов сущностей, поддерживающих
updateOrCreate, создаются:входной тип
_Exist${наименование класса модели}Input;входной тип
_ExistUpdate${наименование класса модели}Input;перечисление с именами уникальных индексов
_Key${наименование класса модели определяющего уникальные индексы};тип
_UpdateOrCreate${наименование класса модели}Response, включающий:атрибут
created: Booleanдля указания на созданиеtrueсущности командой;атрибут
returning: ${наименование класса модели}для чтения атрибутов сущности по аналогии сcreate,update',get.
Для результата команды
updateOrCreateManyопределяется тип_UpdateOrCreateManyResponse, включающий:атрибут
id: IDдля идентификатора созданной сущности;атрибут
created: Booleanдля указания на созданиеtrueсущности командой.
Для типов сущностей, поддерживающий
compareи/илиinc, создается типUpdateMany${наименование класса модели}Input.Для типов сущностей, поддерживающий
compareи/илиinc, создается типDeleteMany${наименование класса модели}Input.
В зависимости от типа свойства соответствующее поле имеет определенный тип:
примитив/перечисление — соответствующий скалярный тип/тип перечисления;
коллекция примитивов/перечислений —
[${наименование соответствующего скалярного типа/типа перечисления}];ссылка —
ID;внешняя ссылка на агрегат —
_SingleReferenceInput;коллекция внешних ссылок на агрегаты —
[_SingleReferenceSetInput];внешняя ссылка на сущность —
_DoubleReferenceInput;коллекция внешних ссылок на агрегаты —
[_DoubleReferenceSetInput].
Пример — элементы для поддержки работы с продуктами и депозитами:
interface Product {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
states(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _StringCollection!
document(alias: String): Document
services(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_Service!
contract: _G_ContractReference!
relatedProducts(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_ProductProductElementReference!
statusForPlatform(alias: String): Status
statusForService(alias: String): Status
}
type _E_Product implements Product & _Entity {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
states(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _StringCollection!
document(alias: String): Document
services(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_Service!
contract: _G_ContractReference!
relatedProducts(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_ProductProductElementReference!
statusForPlatform(alias: String): Status
statusForService(alias: String): Status
}
type _EC_Product {
elems: [Product!]!
count: Int!
}
type _G_ProductReference {
entityId: String
entity: Product
}
type _SE_Product {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
}
type _SEC_Product {
elems: [_SE_Product!]!
count: Int!
}
type _R_Product implements _Reference {
entityId: String
entity: Product
}
input _CreateProductInput {
code: String!
states: [String]
contract: _SingleReferenceInput
relatedProducts: _SingleReferenceSetInput
statusForPlatform: _StatusInput
statusForService: _StatusInput
}
input _UpdateProductInput {
id: ID!
code: String
states: [String]
contract: _SingleReferenceInput
relatedProducts: _SingleReferenceSetInput
statusForPlatform: _StatusInput
statusForService: _StatusInput
}
interface Deposit {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
rate: BigDecimal
states(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _StringCollection!
document(alias: String): Document
services(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_Service!
contract: _G_ContractReference!
relatedProducts(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_ProductProductElementReference!
statusForPlatform(alias: String): Status
statusForService(alias: String): Status
}
type _E_Deposit implements Deposit & Product & _Entity {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
rate: BigDecimal
states(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _StringCollection!
document(alias: String): Document
services(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_Service!
contract: _G_ContractReference!
relatedProducts(cond: String, elemAlias: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_ProductProductElementReference!
statusForPlatform(alias: String): Status
statusForService(alias: String): Status
}
type _EC_Deposit {
elems: [Deposit!]!
count: Int!
}
type _SE_Deposit {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
type: String!
lastChangeDate: _DateTime
chgCnt: Long
code: String!
rate: BigDecimal
}
type _SEC_Deposit {
elems: [_SE_Deposit!]!
count: Int!
}
type _R_Deposit implements _Reference {
entityId: String
entity: Deposit
}
input _CreateDepositInput {
code: String!
rate: BigDecimal
states: [String]
contract: _SingleReferenceInput
relatedProducts: _SingleReferenceSetInput
statusForPlatform: _StatusInput
statusForService: _StatusInput
}
input _UpdateDepositInput {
id: ID!
code: String
rate: BigDecimal
states: [String]
contract: _SingleReferenceInput
relatedProducts: _SingleReferenceSetInput
statusForPlatform: _StatusInput
statusForService: _StatusInput
}
Статусы сущностей#
Если для какой-либо сущности описаны ее наблюдатели, статусы и переходы между ними, то в модели неявно создаются:
класс
Stakeholder(наблюдатель) со свойствами:code: String— код наблюдателя;name: String— наименование наблюдателя;
класс
Status(статус) со свойствами:code: String— код статуса;name: String— наименование статуса;description: String— описание статуса;statusType: String— тип статуса;initial: Boolean— признак является ли статус начальным;stakeholder: Stateholder— ссылка на наблюдателя;
класс
StatusGraph(переход между статусами) со свойствами:code: String— код перехода;name: String— наименование перехода;statusFrom: Status— статус, из которого происходит переход;statusTo: Status— статус, в которой происходит переход.
Для данных классов генерируются те же интерфейсы и типы объектов, как и для обычных классов модели, за исключением:
типа объекта для внешней ссылки;
входных типов для создания/обновления.
Их описания на схеме:
interface Stakeholder {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
lastChangeDate: _DateTime
chgCnt: Long
code: String
name: String
}
type _E_Stakeholder implements _Entity & Stakeholder {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
lastChangeDate: _DateTime
chgCnt: Long
code: String
name: String
}
type _EC_Stakeholder {
elems: [Stakeholder!]!
count: Int!
}
interface Status {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
lastChangeDate: _DateTime
chgCnt: Long
code: String
name: String
description: String
statusType: String
initial: Boolean
stakeholder(alias: String): Stakeholder
}
type _E_Status implements _Entity & Status {
id: ID!
aggVersion: Long!
_getChar(expression: String!): Char
_getString(expression: String!): String
_getByte(expression: String!): Byte
_getShort(expression: String!): Short
_getInt(expression: String!): Int
_getLong(expression: String!): Long
_getFloat(expression: String!): _Float4
_getDouble(expression: String!): Float
_getBigDecimal(expression: String!): BigDecimal
_getDate(expression: String!): _Date
_getDateTime(expression: String!): _DateTime
_getOffsetDateTime(expression: String!): _OffsetDateTime
_getTime(expression: String!): _Time
_getBoolean(expression: String!): Boolean
_getByteArray(expression: String!): _ByteArray
lastChangeDate: _DateTime
chgCnt: Long
code: String
name: String
description: String
statusType: String
initial: Boolean
stakeholder(alias: String): Stakeholder
}
type _EC_Status {
elems: [Status!]!
count: Int!
}
Тип объекта _Packet#
Специализированный тип объекта _Packet используется для описания пакета.
Поля пакета делятся на две группы. К первой группе относятся поля, описывающие результат выполнения уровня пакета:
aggregateVersion: Longсодержит версию агрегата, для которого выполнен пакет;isIdempotenceResponse: Booleanсодержит признак идемпотентного вызова пакета.
Вторая группа полей описывает операции с объектами данных, допустимых к исполнению в пакете. Состав операций основывается на характеристиках пользовательских классов модели данных. В общем случае для каждого класса модели формируются четыре поля:
create${наименование класса модели}(input: _Create${наименование класса модели}Input!): ${наименование класса модели}— определяет команду создания сущности.get${наименование класса модели}(id: ID!): ${наименование класса модели}— определяет команду чтения сущности.update${наименование класса модели}(input: _Update${наименование класса модели}Input!, compare: _Compare${наименование класса модели}, inc: _Inc${наименование класса модели}): ${наименование класса модели}— определяет команду изменения сущности. Опциональный параметрcompareопределяет значения для свойств сущности на соответствие ожидаемым. Опциональный параметрincопределяет значения инкремента свойств сущности.updateOrCreate${наименование класса модели}(input: _Create${наименование класса модели}Input!, exist: _Exist${наименование класса модели}): _UpdateOrCreate${наименование класса модели}Response— выполняет поиск сущности по критериюexist. Если сущность не создана, то создает с параметрамиinput, иначе применяет изменение по параметрамinputили значениями объектаupdateпараметраexistпри их заполнении.delete${наименование класса модели}(id: ID!, compare: _Compare${наименование класса модели}): String— определяет команду удаления сущности. Опциональный параметрcompareопределяет значения для свойств сущности на соответствие ожидаемым.
Для класса модели с указанной возможностью применения прикладной блокировки и всех его потомков дополнительно формируются две операции:
tryLock${наименование класса модели}(input: _TryLockInput!): LockOutput— определяет команду блокировки сущности;unlock${наименование класса модели}(input: _UnlockInput!): LockOutput— определяет команду разблокировки сущности.
Тип объекта _DictionaryPacket#
Специализированный объект описания пакета для модификации справочников. В схеме мутации отражен как dictionaryPacket: _DictionaryPacket.
Пакет имеет ограничения:
нет возможности работать с версией агрегата;
нет возможности выполнять идемпотентные вызовы;
доступны только команды
get,updateOrCreate,updateOrCreateMany,delete,deleteMany.
Внимание!
Команды удаления в пакете по умолчанию отключены. Включение выполняется установкой параметра
dataspace.dictionary.delete.enable=trueсервисаdataspace-core. При использовании команд удаления необходимо учитывать отсутствие контроля ссылочной целостности, что может привести к появлению битых ссылок.
Контроль значений BigDecimal#
Модуль выполняет проверку переданных в пакете команд значений типа BigDecimal на соответствие заданных в модели length и scale.
Детальное изложение в документе "Протокол JSON-RPC".
Использование compare#
Для свойств сущности с типами String, Integer, Long, Date, LocalDate, LocalDateTime, OffsetDateTime можно
определить проверку на соответствие фактических значений сущности ожидаемым. Если по одному из указанных свойств значение не совпадает, то команда завершится ошибкой.
Проверка выполняется до внесения изменений по свойствам.
Пример использования:
mutation {
packet {
createSampleEntity(input: {
code: "sample code"
name: "sample name"
}) {
id
}
updateSampleEntity(
input: {
id: "ref:createSampleEntity"
code: "new sample code"
name: "new sample name"
}
compare: {
code: "sample code"
name: "wrong sample name"
}
) {
code
name
}
}
}
Результат выполнения:
{
"errors": [
{
"message": "Ошибка обработки команды id = 'updateSampleEntity', name = 'update': Ошибка обработки сравниваемого поля 'name': Расхождение ожидаемого 'wrong sample name' и фактического 'sample name' значений",
"locations": [
{
"line": 3,
"column": 3
}
],
"extensions": {
"classification": "COMPARE_NOT_EQUAL"
}
}
],
"data": {
"packet": null
}
}
Ошибка связана с тем, что команда update согласно значениям в compare ожидает, что свойство code должно быть эквивалентным sample code, а свойство name должно быть эквивалентным wrong sample name.
Использование inc#
Для свойств сущности с типами Integer, Long, Float, Double, BigDecimal можно выполнить операцию инкремента текущего значения на указанное в параметрах команды. Передаваемое значение может быть отрицательным для выполнения операции декремента.
Пример использования:
mutation {
packet {
createSampleEntity(
input:{ counter: 9, sum: 3.14 }
) {
id
counter
sum
}
updateSampleEntity(
input: { id: "ref:createSampleEntity" }
inc: {
counter: { value: -4 }
sum: { value: 42 }
}
) {
counter
sum
}
}
}
Результат выполнения:
{
"data": {
"packet": {
"createSampleEntity": {
"id": "7142520608695844865",
"counter": 9,
"sum": 3.14
},
"updateSampleEntity": {
"counter": 5,
"sum": 45.14
}
}
}
}
Параметр inc выполняет операцию сложения текущего значения поля в базе на указанную величину. В примере поле sum увеличено на 42, а поле counter уменьшено на 4.
Выполнение команды создания необходимо для полноты примера.
Существует возможность определения проверки на соответствие вычисленного значения условию.
Если условие выполняется, то новое значение поля является ошибочным, и будет сформировано исключение уровня пакета INC_FAIL_EXCEPTION.
Условие определяется добавлением поля fail в объект описания inc для поля сущности.
Пример:
mutation {
packet {
createSampleEntity(
input:{ sum: 3.14 }
) {
id
sum
}
updateSampleEntity(
input: { id: "ref:createSampleEntity" }
inc: {
sum: {
value: -5
fail: {
operator: lt
value: 0
}
}
}
) {
sum
}
}
}
Результат выполнения:
{
"errors": [
{
"message": "Ошибка обработки команды id = 'updateSampleEntity', name = 'update': Ошибка обработки инкриминируемого поля 'sum': Новое значение поля '-1.86', полученное после сложения с '-5', нарушает ограничение 'LESS 0'",
"locations": [
{
"line": 3,
"column": 3
}
],
"extensions": {
"classification": "INC_FAIL_EXCEPTION"
}
}
],
"data": {
"packet": null
}
}
Описание примера:
первая команда создает экземпляр класса
SampleEntityсо значением3.14в полеsum;вторая команда через параметр
incвыполняет изменение значения поляsumна-5, то есть вычисленное значение3.14 - 5 = -1.86. Параметрincсодержит объектfailс условиемменьше нуля("operator": "lt", "value": 0), при выполнении которого формируется ошибкаINC_FAIL_EXCEPTION.
Использование updateOrCreate#
Команда updateOrCreate выполняет поиск сущности по ключевым критериям. Если сущность найдена, выполняется ее обновление, иначе создается новый экземпляр.
Доступность команды для типа зависит от наличия одного из условий:
категория идентификатора
MANUAL;категория идентификатора
AUTO_ON_EMPTYилиUUIDV4_ON_EMPTY;для категории идентификатора
AUTO(SNOWFLAKE) илиUUIDV4обязательно наличие уникального индекса.
Примечание
Для стратегии наследования
SINGLE_TABLEучитывается доступность индексов предков.
Отношение id и exist#
Параметр input команды заполняется по правилам create, т.е. использование атрибута id зависит от категории идентификатора.
Для категории AUTO_ON_EMPTY или UUIDV4_ON_EMPTY атрибут является опциональным. Логика команды требует определенного критерия, который однозначно идентифицирует сущность.
Если в input заполнен атрибут id, то поиск выполняется по значению этого атрибута. Если атрибут id не используется, то обязательно должен быть заполнен атрибут byKey объекта exist, содержащий имя уникального индекса. Значения для поиска определяются следующим образом:
Используется значение атрибута в
inputдля свойства из состава индекса.Если атрибут не указан, то используется значение по умолчанию для поля.
В противном случае – значение "null".
Частичное обновление сущности#
Выполнение команды для существующей сущности приводит к изменению текущих значений полей на указанные в параметрах команды.
Частичное обновление полей можно выполнить путем заполнения в параметре команды exist объекта с именем update, в котором указываются необходимые к изменению поля и их значения.
Модель технической сущности для демонстрации:
<model>
<class name="SampleEntity">
<id category="AUTO_ON_EMPTY"/>
<property name="code" type="String"/>
<property name="name" type="String"/>
<property name="altKey" type="String" unique="true"/>
</class>
</model>
Схемы GraphQL для этой модели — во вложенном файле graphql-schema-sample-min.
Пример мутации:
mutation {
packet {
updateOrCreateSample(
input: {id: "42", code: "1", name: "1"}
exist: { update: { name: "2" } }
)
{
created
returning {
code
name
}
}
}
}
Первый вызов мутации вернет результат:
{
"data": {
"packet": {
"updateOrCreateSample": {
"created": true,
"returning": {
"code": "1",
"name": "1"
}
}
}
}
}
Второй вызов даст результат:
{
"data": {
"packet": {
"updateOrCreateSample": {
"created": false,
"returning": {
"code": "1",
"name": "2"
}
}
}
}
}
При повторном вызове для существующей сущности с идентификатором 42 выполнено частичное изменение, а именно — изменение поля name. Остальные поля сущности не изменились.
Использование команды get#
Для каждого типа сущности модели в пакете формируется выделенная команда чтения get, позволяющая получить сущность по идентификатору, указанному в аргументе команды id.
Чтение по условию
Базовое поведение команды get позволяет выполнить чтение сущности по ее идентификатору, в случае отсутствия сущности формируется ошибка OBJECT_NOT_FOUND.
Существует возможность чтения сущности по определенному в формате строковых выражений условию.
Результат условия должен обеспечивать получения единственной записи или отсутствие записей. Если по условию найдено несколько сущностей, то будет сформирована ошибка TOO_MANY_RESULTS.
Отсутствие записи не приведет к формированию ошибки, в ответе пакета результатом выполнения команды будет представлен "null".
Условие поиска указывается в аргументе id в формате строкового выражения после префикса find:.
Пример пакета с созданием сущности и разными вариантами чтения:
mutation {
packet {
createSample(input: { code: "sample code" }) { id }
getById: getSample(id: "ref:createSample") { id code }
getByCode: getSample(id:"find:root.code=='sample code'") { id code }
emptyGetByCode: getSample(id:"find:root.code=='unknown sample code'") { id code }
}
}
Результат выполнения:
{
"data": {
"packet": {
"createSample": {
"id": "7139533211695644673"
},
"getById": {
"id": "7139533211695644673",
"code": "sample code"
},
"getByCode": {
"id": "7139533211695644673",
"code": "sample code"
},
"emptyGetByCode": null
}
}
}
В примере последняя команда get возвращает пустой результат, так как сущности с code равным unknown sample code не существует.
Блокирующее чтение
При необходимости выполнять блокирующее чтение сущности уровня БД (select for update). Вариант блокировки определяется атрибутом lock. Пример пакета:
mutation {
packet {
getSample(id:"42", lock: WAIT) { id code }
}
}
Возможные значения lock:
NOT_USER: блокировка не выполняется, отсутствие поляlock(умолчание);WAIT: блокировка ожидания освобождения ресурса;NOWAIT: для блокированной другой транзакцией записи пакет будет завершен ошибкойDATA_ACCESSс текстом сообщения зависящим от используемого драйвера БД.
Ошибка при отсутствующей записи
По умолчанию при чтении сущности для отсутствующей записи:
по идентификатору — формируется ошибка
OBJECT_NOT_FOUND;поисковым условием — ошибки не формируется, результат представлен пустым объектом.
Такое поведение может быть изменено определением значения для аргумента команды failOnEmpty.
Если значение false, то при пустом результате ошибка OBJECT_NOT_FOUND не формируется.
Пример изменения поведения по умолчанию при поиске по идентификатору:
mutation{
packet{
getSample(id:"unknown-entity", failOnEmpty: false) { code }
}
}
Результат выполнения:
{
"data": {
"packet": {
"getSample": null
}
}
}
Внимание!
В пакете с запросом версии агрегата, состоящем только из команд
get, первая команда должна гарантировать получение записи, то есть выполнять чтение по идентификатору без использования строкового условия. ПолеfailOnEmptyдолжно отсутствовать или иметь значениеtrue.
Условное выполнение команд#
Команды пакета выполняются последовательно. Если выполняемая команда завершается ошибкой, то выполнение пакета прерывается и формируется ошибочный результат. Иначе результат пакета будет содержать результат выполнения входящих в него команд.
Существует возможность определить необходимость выполнения команды в пакете на основании результата выполнения другой команды (или других команд).
В качестве источника результата выступают команды getи updateOrCreate. Для работы с условным выполнением команд схема включает следующее:
enum _DependsOnDependencyByGet {
EXISTS
NOT_EXISTS
}
enum _DependsOnDependencyByUpdateOrCreate {
CREATED
NOT_CREATED
}
directive @dependsOnByGet(commandId: String!, dependency: _DependsOnDependencyByGet!) repeatable on FIELD
directive @dependsOnByUpdateOrCreate(commandId: String!, dependency: _DependsOnDependencyByUpdateOrCreate!) repeatable on FIELD
commandId: строковый идентификатор команды источника результата;dependency: строковая константа ожидаемого результата:EXISTS— командаgetс указанным вcommandIdидентификатором имеет не пустой результат;NOT_EXISTS— командаgetс указанным вcommandIdидентификатором имеет пустой результат;CREATED— командаupdateOrCreateс указанным вcommandIdидентификатором имеет значениеcreated, равноеtrue, то есть в результате ее выполнения была создана сущность;NOT_CREATED— командаupdateOrCreateс указанным вcommandIdидентификатором имеет значениеcreated, равноеfalse, то есть сущность была создана ранее.
Команда выполняется, если все директивы имеют условие true. Анализ условий в массиве выполняется до первого результата false.
Команда из директивы должна следовать ранее по потоку выполнения команд пакета, то есть должна быть исполнена на момент проверки.
Для команды updateOrCreate не допустим пустой результат.
Директивы могут быть применены к любым командам, кроме команды get.
В результате пакета пропущенные команды будут отражены null.
Пример пакета с уловным выполнением:
mutation{
packet{
c: updateOrCreateSample(input:{id:"42"})
{ created }
createSample(input:{id:"SUB-42" code: "initial code"})
@dependsOnByUpdateOrCreate(commandId:"c", dependency: CREATED)
{ id code }
g: getSample(id:"SUB-42") { id code }
updateSample(input:{id:"SUB-42" code: "updated code"})
@dependsOnByUpdateOrCreate(commandId:"c", dependency: NOT_CREATED)
@dependsOnByGet(commandId:"g", dependency: EXISTS)
{ code }
}
}
Предполагается, что отсутствуют объекты Sample с id, равным 42.
Первое выполнение пакета даст результат:
{
"data": {
"packet": {
"c": {
"created": true
},
"createSample": {
"id": "SUB-42",
"code": "initial code"
},
"g": {
"id": "SUB-42",
"code": "initial code"
},
"updateSample": null
}
}
}
Пояснение: вторая команда пакета createSample должна быть выполнена только в том случае, если первая команда updateOrCreate создала сущность.
Четвертая команда updateSample должна быть выполнена, если первая команда updateOrCreate не создала сущность, и третья команда getSample вернула не пустой результат.
Второй вызов пакета имеет следующий результат:
{
"data": {
"packet": {
"c": {
"created": false
},
"createSample": null,
"g": {
"id": "SUB-42",
"code": "initial code"
},
"updateSample": {
"code": "updated code"
}
}
}
}
Вторая команда не выполнена, так как сущность уже создана. Четвертая команда выполнена, так как запись существует и не создана первой командой.
Внимание!
Исполнение читающих команд не приводит к созданию записи идемпотентности. Запись создается только для пишущих команд, а именно – для первой пишущей команды. Исходя из этого, первая пишущая команда должна быть безусловной, чтобы запись идемпотентности в любом случае была проверена и при необходимости создана.
Команды Many#
Команды пакета ориентированы на работы с единственным экземпляром сущности.
Для выполнения множественных действий команды create, update, updateOrCreate и delete имеют аналоги Many, принимающие массив аргументов.
Существенным отличием команд Many является упрощение результата выполнения:
createMany${наименование класса модели}результатом является[String]идентификаторов созданных сущностей;updateMany${наименование класса модели}результатом является константаsuccess;deleteMany${наименование класса модели}результатом является константаsuccess;updateOrCreateMany${наименование класса модели}результатом является константа_UpdateOrCreateManyResponse.
Команды в качестве входящего аргумента принимают массив объектов, соответствующих специфике команды.
Массив результата команд createMany и updateOrCreateMany соответствует порядку входящего аргумента.
Результат выполнения команд можно использовать с другими командами через ref: с указанием индекса команды в массиве
результата после имени команды в [].
Пример:
mutation{
packet{
m: createManySample(input:[
{code: "sample 1"},
{code: "sample 2"}
])
g1: getSample(id: "ref:m[0]") { id code }
g2: getSample(id: "ref:m[1]") { id code }
}
}
Результат:
{
"data": {
"packet": {
"m": [
"7139554145819230209",
"7139554145819230210"
],
"g1": {
"id": "7139554145819230209",
"code": "sample 1"
},
"g2": {
"id": "7139554145819230210",
"code": "sample 2"
}
}
}
}
Комплексный пример использования команд Many:
mutation{
packet{
createManySample(input:[ { id: "1" }, { id: "2" } ])
updateManySample(input: [
{param: { id: "1" code: "1" }},
{param: { id: "2" code: "2" }}
])
updateOrCreateManySample(input: [
{
param: { id: "1" code: "10" } exist: { update: { } }
},
{
param: { id: "2" code: "20" } exist: { update: { } }
}
]) { id created }
deleteManySample(input: [
{id: "1", compare: {code: "1"}},
{id: "2", compare: {code: "2"}}
])
}
}
Результат выполнения:
{
"data": {
"packet": {
"createManySample": [
"1",
"2"
],
"updateManySample": "success",
"updateOrCreateManySample": [
{
"id": "1",
"created": false
},
{
"id": "2",
"created": false
}
],
"deleteManySample": "success"
}
}
}
Пояснение: команда updateOrCreateManySample не создает сущности, так как они были созданы ранее, и в варианте пустого объекта update поля exist не выполняет изменение поля code, что фиксируется при выполнении команды deleteManySample заполнением параметра compare.
Для команд Many допускается условное выполнение. Результат выполнения пропущенной команды в результате будет null.
Пример — поля пакета для работы с продуктом#
type _Packet {
aggregateVersion: Long
isIdempotenceResponse: Boolean
# ...
createProduct(input: _CreateProductInput!): Product
updateProduct(input: _UpdateProductInput!): Product
deleteProduct(id: ID!): String
getProduct(id: ID!): Product
tryLockProduct(input: _TryLockInput!): _LockOutput
unlockProduct(input: _UnlockInput!): _LockOutput
# ...
}
Запрос#
Запрос в схеме представлен объектом с наименованием _Query. Объект включает в себя следующие поля:
merge(limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _MergedEntitiesCollection!— для слияния поисковых запросов, где:limit— ограничение на количество элементов;offset— смещение;sort— сортировка.
search${наименование класса модели}(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]) : _EC_${наименование класса модели}!— для каждого класса модели, для поиска сущности соответствующего класса, где:cond— условие фильтрации в грамматике строковых выражений;limit— ограничение на количество элементов;offset— смещение;sort— сортировка.
search${наименование класса модели}History(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]) : _EC_${наименование класса модели}!— для каждого класса модели из цепочки наследования, содержащей историцируемые поля, для поиска по истории изменения сущности, где:cond— условие фильтрации в грамматике строковых выражений;limit- ограничение на количество элементов;offset— смещение;sort— сортировка.
selectionBy${наименование класса модели}(cond: String, distinct: Boolean, group: [String!], groupCond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _SEC_${наименование класса модели}!— для каждого класса модели, для выборки свойств на основе поиска сущности одного типа, distinct и groupBy поисков, где:cond— условие фильтрации в грамматике строковых выражений;limit— ограничение на количество элементов;offset— смещение;sort— сортировка,distinct— признак выбора уникальных картежейgroup— условия группировки в грамматике строковых выражений;groupCond— условия фильтрации после группировки (аналог having) в грамматике строковых выражений.
getState${наименование класса модели}(id: String!, date: _OffsetDateTime!)— для каждого класса модели из цепочки наследования, содержащей историцируемые поля, возвращает состояние историцируемых атрибутов сущности на заданный момент времени, где:id— идентификатор сущности для которой определяется состояние;date— время на которое определяется состояние сущности.
getStates${наименование класса модели}(id: String!, timeFrom: _OffsetDateTime, timeTo: _OffsetDateTime, limit: Int, offset: Int)— для каждого класса модели из цепочки наследования, содержащей историцируемые поля, возвращает список состояний сущности, где:id— идентификатор сущности для которой определяется состояние;timeFrom— ограничение временного диапазона поиска снизу;timeTo— ограничение временного диапазона поиска сверху;limit— ограничение на количество элементов;offset— смещение.
get${наименование класса модели}History(id: String!, timeFrom: _OffsetDateTime, timeTo: _OffsetDateTime, limit: Int, offset: Int)— для каждого класса модели из цепочки наследования, содержащей историцируемые поля, возвращает список изменений сущности, где:id— идентификатор сущности для которой определяется состояние;timeFrom— ограничение временного диапазона поиска снизу;timeTo— ограничение временного диапазона поиска сверху;limit— ограничение на количество элементов;offset— смещение.
resolveReferences(referenceType: String!, ids: [ID!]!): [_Reference!]!— для разрешения ссылок, где:referenceType— тип ссылок, которые будут возвращены в списке;ids— идентификаторы сущностей, для которых необходимо разрешить ссылки.
multisearch${наименование класса модели}(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!], ctx: String) : _EC_${наименование класса модели}!— для каждого класса модели, для межшардового поиска сущности соответствующего класса, где:cond— условие фильтрации в грамматике строковых выражений;limit— ограничение на количество элементов;offset— смещение;sort— сортировка;ctx— контекст мультипоиска, сериализованный в строку. Пример контекста можно найти в разделе "Использование мультипоиска" документа "Руководство прикладного разработчика".
multimerge(limit: Int, offset: Int, sort: [_SortCriterionSpecification!], ctx: String): _MergedEntitiesCollection!— для слияния поисковых запросов, где:limit— ограничение на количество элементов;offset— смещение;sort— сортировка;ctx— контекст мультипоиска, сериализованный в строку. Пример контекста можно найти в разделе "Использование мультипоиска" документа "Руководство прикладного разработчика".
При заданной настройке
dataspace.endpoint.graphql.schema.settings.generate-str-expr-field=truestrExpr(...)— для использования переменных в выражениях в терминах грамматики строковых выражений, с аргументами:chars: [Char!]— символы;strings: [String!]— строки;bytes: [Byte!]— целые числа (1 байт);shorts: [Short!]— целые числа (2 байта);ints: [Int!]— целые числа (4 байта);longs: [Long!]— целые числа (8 байт);floats: [_Float4!]— вещественные числа (4 байта);doubles: [Float!]— вещественные числа (8 байт);bigDecimals: [BigDecimal!]— большие десятичные числа;dates: [_Date!]— даты;dateTimes: [_DateTime!]— даты и времена;offsetDateTimes: [_OffsetDateTime!]— даты и времена со смещением;times: [_Time!]— времена;booleans: [Boolean!]— логические значения;byteArrays: [_ByteArray!]— массивы байтов.
Пример — поля запроса для работы с продуктом:
type _Query {
merge(limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _MergedEntitiesCollection!
# ...
searchProduct(cond: String, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _EC_Product!
selectionByProduct(cond: String, distinct: Boolean, limit: Int, offset: Int, sort: [_SortCriterionSpecification!]): _SEC_Product!
# ...
resolveReferences(referenceType: String!, ids: [ID!]!): [_Reference!]!
}
Мутация#
Мутация в схеме представлена объектом с наименованием _Mutation.
Данный объект содержит:
Поле
packet(aggregateVersion: Long, idempotencePacketId: String): _Packet, где:aggregateVersion— версия агрегата для оптимистической блокировки. Если аргумент не указан, то проверка выполняться не будет. Версия агрегата после выполнения пакета может быть получена через запрос поляaggregateVersionтипа объекта_Packet.idempotencePacketId— ключ идемпотентности пакета. Если аргумент не указан, то пакет не будет проверяться на идемпотентность.
Поле
dictionaryPacket: _DictionaryPacketпри наличии в модели справочников;При заданной настройке
dataspace.endpoint.graphql.schema.settings.generate-str-expr-field=truestrExpr(...)— для использования переменных в выражениях в терминах грамматики строковых выражений, с аргументами:chars: [Char!]— символы;strings: [String!]— строки;bytes: [Byte!]— целые числа (1 байт);shorts: [Short!]— целые числа (2 байта);ints: [Int!]— целые числа (4 байта);longs: [Long!]— целые числа (8 байт);floats: [_Float4!]— вещественные числа (4 байта);doubles: [Float!]— вещественные числа (8 байт);bigDecimals: [BigDecimal!]— большие десятичные числа;dates: [_Date!]— даты;dateTimes: [_DateTime!]— даты и времена;offsetDateTimes: [_OffsetDateTime!]— даты и времена со смещением;times: [_Time!]— времена;booleans: [Boolean!]— логические значения;byteArrays: [_ByteArray!]— массивы байтов.
Использование поля packet в мутации определяет границу транзакции в базе данных при выполнении формируемой мутации.
Тип объекта _Mutation в схеме:
type _Mutation {
packet(aggregateVersion: Long, idempotencePacketId: String): _Packet
}
Если модель содержит классы справочников, то объект мутации будет иметь вид:
type _Mutation {
packet(aggregateVersion: Long, idempotencePacketId: String): _Packet
dictionaryPacket: _DictionaryPacket
}
Подписка#
Внимание!
При увеличении количества pods (DropApp/K8s/OpenShift) сервиса DataSpace Subscription в целях горизонтального масштабирования, при увеличивающейся нагрузке на GraphQL Subscriptions, необходимо
пересоздать активные GraphQL Subscriptionsдля перераспределения нагрузки с учётом новых добавленных pods.Пересоздать активные GraphQL Subscriptionsозначает, что необходимо завершить активные GraphQL Over WebSocket запросы на точку доступа{серверURL}/subscriptions, после чего выполнить их снова. Если не провести перераспределение активных GraphQL Subscriptions с учётом добавленных pods, то возможно увеличение времени обработки событий по активным GraphQL Subscriptions.
Важно!
Настройка graphql.servlet.actuator-metrics = true негативно влияет на производительность в части потребления памяти. При использовании функционала GraphQL Subscriptions настоятельно рекомендуется установить значение graphql.servlet.actuator-metrics = false
Взаимодействие с API подписок GraphQL Subscriptions осуществляется по протоколу GraphQL Over WebSocket subscriptions-transport-ws через точку доступа со следующим URL-адресом: {серверURL}/subscriptions.
Для использования API подписок GraphQL Subscriptions должны быть выполнены следующие условия:
В модели данных описано хотя бы одно событие. Подробнее об описание событий в модели данных см. в разделе "Требования к модели" документа "Руководство по работе с сервисом DataSpace Subscription".
Сервис DataSpace Subscription настроен и развернут. Если используется объединенный модуль DataSpace Bundle, то необходимо установить настройку
dataspace.subscription.enable=trueперед его запуском. Подробнее о настройке сервиса DataSpace Subscription для интеграции сGraphQL Subscriptionsсм. в разделе "Интеграция с GraphQL Subscriptions" документа "Руководство по работе с сервисом DataSpace Subscription".Настройка
dataspace.graphql.subscriptions.enabledв сервисах DataSpace Core и DataSpace Subscription установлена в значение "true" (по умолчанию оба сервиса содержат данную настройку со значением "true").Настройка
dataspace.graphql.subscriptions.subscriptionServiceGrpcUrlсервисаDataSpace Coreзадана в соответствии с форматомhttp://<host>:<port>, где<host>— URL для подключения к сервису DataSpace Subscription,<port>— соответствует настройкеdataspace.subscription.grpcServerPortсервиса DataSpace Subscription.
Подписка в схеме представлена объектом с наименованием _Subscription. Объект включает в себя следующие поля:
${наименование класса события модели с маленькой буквы}Subscription(criteria: String) : _EC_${наименование класса события модели}! —
для класса события модели, для подписки на событие соответствующего класса, где criteria — условие фильтрации в грамматике строковых выражений.
Пример — событие, описанное в модели, и поле подписки на событие создания продукта:
<event name="ProductCreateEvent">
<property name="message" type="String"/>
<property name="product" type="Product" parent="true"/>
</event>
type _Subscription {
productCreateEventSubscription(criteria: String): _EC_ProductCreateEvent
}
Пример итоговой схемы#
Пример схемы GraphQL можно увидеть во вложенном файле graphql-schema-big.
Виды запросов#
Поиск#
Для поиска сущностей определенного типа необходимо запрашивать search-поле, соответствующее классу модели.
Пример поиска продукта (Product) с идентификатором, равным "1", с запросом кода (code):
{
searchProduct(cond: "it.$id == '1'") {
elems {
code
}
}
}
С помощью аргументов limit, offset можно управлять настройками пагинации. С помощью аргумента sort можно задавать настройки сортировки. А запросив поле count, можно получить общее количество элементов, удовлетворяющих условию поиска.
Ниже приведен пример поиска продуктов (Product) с кодом (code):
соответствующим шаблону
product%;с запросом идентификатора и кода;
отсортированных по коду по возрастанию, а затем по идентификатору по убыванию;
с отображением только 10 элементов, пропуская первые 20;
с запросом общего количества продуктов, удовлетворяющих условию поиска.
{
searchProduct(cond: "it.code $like 'product%'", sort: [{crit: "it.code"}, {crit: "it.$id", order: DESC}], limit: 10, offset: 20) {
elems {
id
code
}
count
}
}
С помощью аргументов cond, limit, offset, sort для свойств-коллекций примитивов/ссылок можно также управлять настройками фильтрации, пагинации и сортировки, а запросив поле count, получить общее количество элементов, удовлетворяющих условию фильтрации.
Ниже приведен пример поиска продукта (Product):
с идентификатором, равным
1;с запросом состояний (
states):со значением, соответствующим шаблону state%;
отсортированных по значению по возрастанию;
с отображением только 10 элементов, пропуская первые 20;
с запросом общего количества состояний, удовлетворяющих условию фильтрации;
с запросом сервисов (
services):с кодом (
code) соответствующим шаблону service%;с запросом идентификатора и кода;
отсортированных по коду по возрастанию, а затем по идентификатору по убыванию;
с отображением только 10 элементов, пропуская первые 20;
с запросом общего количества продуктов, удовлетворяющих условию фильтрации.
{
searchProduct(cond: "it.$id == '1'") {
elems {
states(cond: "it $like 'state%'", sort: {crit: "it"}, limit: 10, offset: 20) {
elems
count
}
services(cond: "it.code $like 'service%'", sort: [{crit: "it.code"}, {crit: "it.$id", order: DESC}], limit: 10, offset: 20) {
elems {
id
code
}
count
}
}
}
}
С помощью аргументов alias для свойств-ссылок и elemAlias для свойств-коллекций ссылок можно задавать псевдоним для ссылки/элемента коллекции ссылок, который можно использовать для фильтрации вложенных коллекций.
Ниже приведен пример поиска продукта (Product):
с идентификатором, равным 1;
с запросом документа (
document):с запросом состояний (
states) со значением, соответствующим шаблону код документа (code) +state%;
с запросом сервисов (
services) с запросом состояний(states) со значением, соответствующим шаблону код сервиса (code) +state%.
{
searchProduct(cond: "it.$id == '1'") {
elems {
document(alias: "document") {
states(cond: "it $like @document.code + 'state%'") {
elems
}
}
services(elemAlias: "service") {
elems {
states(cond: "it $like @service.code + 'state%'") {
elems
}
}
}
}
}
}
С помощью фрагментов по интерфейсам можно запрашивать детализацию типа, то есть запрашивать дополнительные поля для классов-потомков.
Ниже приведен пример поиска продуктов (Product):
с кодом (
code), соответствующим шаблонуproduct%;с запросом идентификатора и кода;
с запросом даты начала действия (
beginDate) в случае, если продукт является депозитом (Deposit).
{
searchProduct(cond: "it.code $like 'product%'") {
elems {
id
code
... on Deposit {
beginDate
}
}
}
}
Битые ссылки#
При попытке обращения к сущности через битую ссылку будет получена ошибка как в примере ниже.
Запрос:
searchProduct(cond: "root.$id == '${product1Id}'") {
elems {
relatedProduct {
code
}
request {
initiator {
firstName
lastName
}
}
}
}
Ответ:
"errors": [
{
"message": "Reference of type Product with id = nonexistentId is invalid",
"path": [
"searchProduct",
"elems",
0,
"relatedProduct"
],
"extensions": {
"classification": "InvalidData"
}
}
],
"data": {
"searchProduct": {
"elems": [
{
"relatedProduct": null,
"request": {
"initiator": {
"firstName": "Vasya",
"lastName": "Vasiliev"
}
}
}
]
}
}
Поиск уникальных значений#
Для поиска уникальных значений (картежей) необходимо запрашивать selectionBy-поле, соответствующее классу модели, задав distinct: true.
Пример поиска уникальных пар code, name продукта (Product):
{
selectionByProduct(cond: "it.code $like 'abc%'", distinct: true) {
elems {
code
name
}
}
}
Запрос с группировкой#
Для запроса с группировкой необходимо запросить selectrionBy-поле, соответствующее классу модели, задав group атрибут.
Пример группировки выборки по code и name с подсчетом количества записей и дополнительной фильтрацией записей, количество которых более трех:
{
selectionByProduct(cond: "it.code $like 'abc%'", group: ["it.code", "it.name"], groupCond: "it.$id.$count > 3") {
elems {
code
name
count: _getInt(expression: "it.$id.$count")
}
}
}
Слияние запросов#
Для слияния поисковых запросов по несвязанным наследованием типам, необходимо запрашивать merge-поле. Затем с помощью встроенных фрагментов по интерфейсам, соответствующим классам модели, запросы по которым нужно слить, и директивы mergeReqSpec необходимо оформить поисковые запросы, которые нужно слить, по следующим правилам:
Для поисковых запросов можно задать только условие поиска в аргументе
condдирективыmergeReqSpec.В аргументах
limitиoffsetполя можно задать настройки пагинации, действующие на все слияние.В аргументе
sortполя можно задать настройки сортировки, действующие на все слияние с единственным ограничением: критерии сортировки должны быть применимы к каждому поисковому запросу по отдельности.С помощью запроса поля
countможно получить общее количество элементов.
Ниже приведен пример слияния запросов:
Поиск продуктов (
Product) с кодом (code), соответствующим шаблонуproduct%; с запросом идентификатора и кода; с запросом даты начала действия (beginDate) в случае, если продукт является депозитом (Deposit).Поиск сервисов (
Service) с кодом (code), соответствующим шаблонуservice%; с запросом идентификатора и состояний (states).Поиск документа (
Document) с идентификатором в диапазоне[1, 2, 3]; с запросом продукта (product); с запросом кода (code).
С условиями:
с сортировкой по коду по возрастанию, а затем по идентификатору по убыванию;
с отображением только 10 элементов, пропуская первые 20;
с запросом общего количества продуктов, удовлетворяющих условию поиска.
{
merge(sort: [{crit: "it.code"}, {crit: "it.$id", order: DESC}], limit: 10, offset: 20) {
elems {
... on Product @mergeReqSpec(cond: "it.code $like 'product%'") {
id
code
... on Deposit {
beginDate
}
}
... on Service @mergeReqSpec(cond: "it.code $like 'service%'") {
id
states {
elems
}
}
... on Document @mergeReqSpec(cond: "it.$id $in ['1', '2', '3']") {
product {
code
}
}
}
count
}
}
Запрос состояния историцируемой сущность на момент времени#
{
getStateProduct(id: "7067525912085069825", date: "2022-02-22T16:32:00Z") {
code
name
}
}
Запрос списка состояний историцируемой сущности#
{
getStatesProduct(id: "7067525912085069825") {
elems {
sysHistoryTime
sysHistNumber
code
sysCodeUpdated
name
sysNameUpdated
}
count
}
}
Запрос списка изменений сущности#
{
getProductHistory(id: "7067525912085069825") {
elems {
sysHistoryTime
sysHistNumber
code
sysCodeUpdated
name
sysNameUpdated
}
count
}
}
Межшардовый поиск (мультипоиск)#
Пример запроса сущностей:
{
multisearchProductParty(cond: "it.code!=null", limit: 10, sort: [{crit:"it.name"}], ctx: "{\"shards\":{\"shard1\":3, \"shard2\":7}, \"offset\":0, \"limit\":10,\"checksum\":138034844,\"lastPageSize\":10}"){
elems{
code
name
}
count
ctx
}
}
Пример слияния поисковых запросов в мультипоиске:
{
multimerge(sort: [{crit: "it.code"}, {crit: "it.$id", order: DESC}], limit: 10, ctx: "{\"shards\":{\"shard2\":4,\"shard1\":6},\"offset\":0,\"limit\":10,\"checksum\":2308350222,\"lastPageSize\":10}") {
elems {
... on ProductParty @mergeReqSpec(cond: "it.code!=null") {
id
code
}
... on RequestInst @mergeReqSpec(cond: "it.code!=null") {
id
}
}
count
ctx
}
}
Разрешение ссылок#
Поле для разрешения ссылок предназначено для поддержки функционирования GraphQL федерации, объединяющей несколько GraphQL-схем.
Для разрешения ссылок необходимо в resolveReferences-поле передать тип ссылок, который определяет какого типа ссылки будут возвращены, а также перечь идентификаторов.
Пример — разрешение ссылок на продукты (Product) с идентификаторами равными 1, 2 и 3; с запросом кода (code):
{
resolveReferences(referenceType: "_R_Product", ids: ["1", "2", "3"]) {
... on _R_Product {
entity {
code
}
}
}
}
Запрос подписки#
Тело запроса подписки идентично телу запроса поиска через search-поле. Подробнее см. раздел "Поиск".
Для того чтобы была возможность запрашивать данные объекта, связанного с событием, на которое осуществлена подписка, необходимо в событие добавить ссылку на родительский класс:
<event name="ProductCreateEvent">
<property name="message" type="String"/>
<property name="product" type="Product" parent="true"/>
</event>
Пример запроса на создание подписки на события ProductCreateEvent с условием фильтрации по коду родительской сущности Product (выполняется выборка свойств самого события ProductCreateEvent, а также свойств родительского объекта product с фильтрацией коллекционного свойства services):
subscription {
productCreateEventSubscription(criteria: "root.product.code == 'productCode'") {
elems {
id
type
code
product {
code
name
client {
entityId
}
offsetDateTime
services(cond: "it.personnelNumber > 100") {
elems {
code
personnelNumber
}
}
}
}
}
}
Результат:
{
"productCreateEventSubscription": {
"elems": [
{
"id": "7342826409546743816",
"type": "SampleProductPartyEvent",
"code": "eventCode",
"product": {
"code": "productCode",
"name": "productName",
"client": {
"entityId": "1001"
},
"offsetDateTime": "2020-03-12T11:37:48.236Z",
"services": {
"elems": [
{
"code": "serviceCode1",
"personnelNumber": 234
},
{
"code": "serviceCode2",
"personnelNumber": 345
}
]
}
}
}
]
}
}
Пакет#
Для изменения данных используется пакет операций, которые выполняются в одной транзакции. В пакете могут выполняться операции по нескольким сущностям модели данных. Последовательность выполнения операций над объектами определяется порядком определения полей типа объекта _Packet. Чтение состояния сущностей в пакете будут возвращать промежуточное состояние в ходе выполнения транзакции.
Пример создания продукта (Product)
Запрос:
mutation {
packet {
createProduct(input: {
code: "product1"
}) {
id
code
}
}
}
Результат:
{
"data": {
"packet": {
"createProduct": {
"id": "6934251168182108161",
"code": "product1"
}
}
}
}
В пакете могут быть выполнены операции над несколькими сущностями одного агрегата. Если в классе модели используется автоматическое формирование идентификатора, то для указания ссылки на создаваемый в пакете объект используется специализированный синтаксис описания идентификатора ref:{псевдоним/наименование поля создания объекта}.
Пример создания продукта (Product) и связанного с ним сервиса (Service)
Запрос:
mutation {
packet {
createProduct(input: {
code: "product1"
}) {
id
}
createService(input: {
product: "ref:createProduct"
code: "service1"
}) {
id
product {
id
code
}
}
}
}
Результат:
{
"data": {
"packet": {
"createProduct": {
"id": "6934253088032489473"
},
"createService": {
"id": "6934253088032489474",
"product": {
"id": "6934253088032489473",
"code": "product1"
}
}
}
}
}
Пример создания продукта (Product) и связанного с ним сервиса (Service) (с использованием псевдонима)
Запрос:
mutation {
packet {
product1: createProduct(input: {
code: "product1"
}) {
id
}
createService(input: {
product: "ref:product1"
code: "service1"
}) {
id
product {
id
code
}
}
}
}
Результат:
{
"data": {
"packet": {
"product1": {
"id": "6934253088032489473"
},
"createService": {
"id": "6934253088032489474",
"product": {
"id": "6934253088032489473",
"code": "product1"
}
}
}
}
}
Пример создания и обновление продукта (Product) с чтением промежуточных состояний
Запрос:
mutation {
packet {
product1: createProduct(input: {code: "product1"}) {
id
code
}
product1_afterCreate: getProduct(id: "ref:product1") {
id
code
}
product1_updated: updateProduct(input: {id: "ref:product1", code: "product1_new"}) {
id
code
}
product1_afterUpdate: getProduct(id: "ref:product1") {
id
code
}
}
}
Результат:
{
"data": {
"packet": {
"product1": {
"id": "6934262438176292865",
"code": "product1"
},
"product1_afterCreate": {
"id": "6934262438176292865",
"code": "product1"
},
"product1_updated": {
"id": "6934262438176292865",
"code": "product1_new"
},
"product1_afterUpdate": {
"id": "6934262438176292865",
"code": "product1_new"
}
}
}
}
В одной операции мутации может быть использовано несколько пакетов, но каждый пакет является самостоятельной транзакционной единицей.
Пример создания двух независимых продуктов (Product)
Запрос:
mutation {
packet1: packet {
createProduct(input:{ code: "product1" }) {
id
}
}
packet2: packet {
createProduct(input:{ code: "product2" }) {
id
}
}
}
Результат:
{
"data": {
"packet1": {
"createProduct": {
"id": "6934264250652491777"
}
},
"packet2": {
"createProduct": {
"id": "6934264250652491778"
}
}
}
}
Для обеспечения идемпотентного вызова пакета необходимо указать атрибут idempotencePacketId поля packet. Опциональное поле isIdempotenceResponse определяет состояние выполнения пакета.
Пример создания продукта (Product) в идемпотентном вызове с проверкой состояния выполнения пакета
Запрос:
mutation {
packet(idempotencePacketId: "1") {
isIdempotenceResponse
createProduct(input: {code: "product1"}) {
id
}
}
}
Результат:
{
"data": {
"packet": {
"isIdempotenceResponse": false,
"createProduct": {
"id": "6934265174070460417"
}
}
}
}
Значение isIdempotenceResponse, равное False, указывает на то, что операция была фактически выполнена и создан новый объект. При повторном вызове этого кода результат будет следующий:
{
"data": {
"packet": {
"isIdempotenceResponse": true,
"createProduct": {
"id": "6934265174070460417"
}
}
}
}
Значение isIdempotenceResponse, равное True, указывает на то, что операция не была выполнена и получен ранее созданная сущность. Совпадение идентификаторов подтверждают это. Важно отметить, что идемпотентными являются операции создания и изменения сущности, но чтение данных выполняется всегда.
Пример создания продукта (Product) и сервиса (Service) с последующим удалением сервиса в идемпотентном вызове
Запрос:
mutation {
packet(idempotencePacketId: "1") {
createProduct(input: {code: "product1"}) {
id
}
createService(input: {product: "ref:createProduct", code: "service1"}) {
id
}
deleteService(id: "ref:createService")
}
}
Результат:
{
"data": {
"packet": {
"createProduct": {
"id": "6934272192047022081"
},
"createService": {
"id": "6934272192047022082"
},
"deleteService": "success"
}
}
}
При повторном выполнении этого кода возникнет следующая ошибка:
{
"errors": [
{
"message": "Ошибка обработки команды id = 'createService#GET4GQL', name = 'get': Не найден экземпляр типа 'Service' с идентификатором '6934272192047022082'",
"locations": [],
"extensions": {
"classification": "OBJECT_NOT_FOUND"
}
}
],
"data": {
"packet": null
}
}
Для демонстрации работы оптимистической блокировки в пакете на первом шаге создается продукт (Product) с запросом текущей версии агрегата:
mutation {
packet {
aggregateVersion
createProduct(input: {code: "product1"}) {
id
}
}
}
Результат:
{
"data": {
"packet": {
"aggregateVersion": 1,
"createProduct": {
"id": "6934266763208359937"
}
}
}
}
На втором шаге выполняется обновление объекта с указанием текущей версии и запросом новой версии:
mutation {
packet(aggregateVersion: 1) {
aggregateVersion
updateProduct(input: {id: "6934266763208359937", code: "product1_new"}) {
id
}
}
}
Результат:
{
"data": {
"packet": {
"aggregateVersion": 2,
"updateProduct": {
"id": "6934266763208359937"
}
}
}
}
Если повторить мутацию второго шага, то результат выполнения будет содержать ошибку:
{
"errors": [
{
"message": "Version 1 required but found 2 for object 6934266763208359937#local.coreaslib.sdk.jpa.Product",
"locations": [],
"extensions": {
"classification": "AGGREGATE_VERSION_EXCEPTION"
}
}
],
"data": {
"packet": null
}
}
Использование переменных в выражениях в терминах грамматики строковых выражений#
В выражениях в терминах грамматики строковых выражений допускается использование значений переменных через конструкцию ${наименование переменной}.
Если переменная-примитив нигде в запросе более не используется, то необходимо пометить ее директивой @strExpr или с помощью поля strExpr (в зависимости от настроек сервера) с указанием соответствующего типа аргумента со значением данной переменной.
Далее приведены варианты примеров поиска продукта (Product) по коду (code).
Вариант 1 (директивы на определениях переменных):
query myQuery(
$code: String! @strExpr(string: $code),
$name: String! @strExpr(string: $name)
) {
searchProduct(cond: "it.code == ${code} && it.name == ${name}") {
elems {
id
}
}
}
Вариант 2 (директива на поле):
query myQuery(
$code: String!,
$name: String!
) {
searchProduct(cond: "it.code == ${code} && it.name == ${name}") @strExpr(strings: [$code, $name]) {
elems {
id
}
}
}
Вариант 3 (поле strExpr):
query myQuery(
$code: String!,
$name: String!
) {
strExpr(strings: [$code, $name])
searchProduct(cond: "it.code == ${code} && it.name == ${name}") {
elems {
id
}
}
}
Имеется возможность обращаться к полям переменных-объектов через символ ..
Пример создания сервиса (Service) продукта (Product) с запросом сервисов продукта с таким же кодом (code):
mutation myMutation($serviceInput: _CreateServiceInput!) {
packet {
createService(input: $serviceInput) {
id
product {
services(cond: "it.code == ${serviceInput.code}") { elems { id } }
}
}
}
}
Имеется возможность обращаться к элементам переменных-массивов через конструкцию [индекс].
Пример создания нескольких сервисов (Service) продукта (Product) с запросом кода (code) продукта:
mutation myMutation($serviceInputs: [_CreateServiceInput!]!) {
packet {
createManyService(input: $serviceInputs)
getProduct(id: "find: it.id == ${serviceInputs[0].product}") { code }
}
}
Имеется возможность обращаться к переменным-массивам, которые вставляются в выражение через запятую. Также можно сконструировать такой массив на основе массива объектов и свойства через символ ..
Пример создания нескольких сервисов (Service) продукта (Product) с запросом сервисов продукта с такими же кодами (code):
mutation myMutation($serviceInputs: [_CreateServiceInput!]!) {
packet {
createManyService(input: $serviceInputs)
getProduct(id: "find: it.id == ${serviceInputs[0].product}") {
services(cond: "it.code $in [${serviceInputs.code}]") { elems { id } }
}
}
}
Другие примеры#
Работа с датами#
Поиск рейсов из Москвы во Владивосток между 11.09.2023 и 17.09.2023, которые отправляются до 12:00:
{
searchFlight(cond: """
it.departureAirport.city.code == 'MOW' &&
it.arrivalAirport.city.code == 'VVO' &&
it.departureDate.$date $between (D2023-09-11, D2023-09-17) &&
it.departureDate.$time <= T12:00
""") {
elems {
no
departureDate
arrivalDate
departureAirport {
code
city {
name
}
}
arrivalAirport {
code
city {
name
}
}
}
}
}
Примечание
Предполагается, что даты хранятся с типом LocalDateTime в часовой зоне аэропорта.
Работа с переменными#
В разделе приведены варианты примеров поиска свободных (не имеющих брони на заданные даты) комнат с возможностью уточнения диапазона цен и наличия мини-бара.
Вариант 1:
query searchRoom(
$beginDate: _Date! @strExpr(date: $beginDate),
$endDate: _Date! @strExpr(date: $endDate),
$startPrice: BigDecimal! @strExpr(bigDecimal: $startPrice),
$endPrice: BigDecimal! @strExpr(bigDecimal: $endPrice),
$minibar: Boolean! = false @strExpr(boolean: $minibar)
) {
searchRoom(cond: """
!it.reservations{cond = ${endDate} >= it.beginDate && it.endDate <= ${beginDate}}.$exists &&
room.price $between (${startPrice}, ${endPrice}) &&
room.minibar == ${minibar}
""") {
elems {
id
price
}
}
}
Переменные:
{
"beginDate": "2023-06-01",
"endDate": "2023-06-10",
"startPrice": 5000.0,
"endPrice": 10000.0,
"minibar": true
}
Вариант 2:
query searchRoom(
$beginDate: _Date!,
$endDate: _Date!,
$startPrice: BigDecimal!,
$endPrice: BigDecimal!,
$minibar: Boolean! = false
) {
searchRoom(cond: """
!it.reservations{cond = ${endDate} >= it.beginDate && it.endDate <= ${beginDate}}.$exists &&
room.price $between (${startPrice}, ${endPrice}) &&
room.minibar == ${minibar}
""") @strExpr(dates: [$beginDate, $endDate], bigDecimals: [$startPrice, $endPrice], boolean: $minibar) {
elems {
id
price
}
}
}