Протокол GraphQL компонента DataSpace Core#
Введение#
Компонент 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=....
Элементы схемы#
Примитивные типы#
Примитивные типы модели отображаются в следующие скалярные типы схемы:
Примитивный тип модели |
Скалярный тип схемы |
Формат |
Пример значения |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
В случае, если в модели используются коллекции примитивов, то для них дополнительно инициализируются типы объектов
_${наименование скалярного типа без префикса '_'}Collection со следующими полями:
elems: [${наименование скалярного типа}!]!: элементы коллекции;count: Int!: количество элементов в коллекции.
Пример: коллекция символов и коллекция дат#
type _CharCollection {
elems: [Char!]!
count: Int!
}
type _DateCollection {
elems: [_Date!]!
count: Int!
}
Технические элементы#
Для поддержки работы основных функций на схеме введены различные технические элементы:
директива
mergeReqSpec(спецификация запроса для слияния) для встроенных фрагментов с аргументомcond: String!: условие поиска в грамматике строковых выражений;интерфейс
_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: идентификатор сущности.
Описания элементов на схеме:
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
}
Типы перечисления#
Типы перечислений модели отображаются в типы перечислений схемы _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!: версия агрегата сущности;поля для вычислимых свойств, имеющих вид
_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений;поля для каждого свойства класса модели, включая свойства родительского класса.
Создается тип объекта
_E_${наименование класса модели}, реализующий: * интерфейс_Entity; * интерфейс соответствующий классу модели; * интерфейсы, соответствующие всем родительским классам модели. Данный тип объекта с полями: *id: ID!— идентификатор сущности; *aggVersion: Long!— версия агрегата сущности; * поля для вычислимых свойств, имеющих вид_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений; * поля для каждого свойства класса модели включая свойства родительского класса.Создается тип для коллекции сущностей
_EC_${наименование класса модели}с полями:elems: [${интерфейс класса модели}!]!— элементы коллекции;count: Int!— количество элементов в коллекции.
Если имеются внешние ссылки на данный класс модели, то создается тип для внешней ссылки
_G_${наименование класса модели}Referenceс полями:entityId: String— идентификатор сущности;rootEntityId: String— идентификатор агрегата сущности (есть только в случае, если класс модели не является агрегатом);entity: ${наименование класса модели}— ссылка на сущность для запроса данных в текущем шарде.
Создается тип для набора свойств сущности
_SE_${наименование класса модели}с полями:id: ID!— идентификатор сущности;aggVersion: Long!— версия агрегата сущности;поля для вычислимых свойств, имеющих вид
_get${наименование скалярного типа}(expression: String!): ${наименование скалярного типа}, гдеexpression— выражение в терминах грамматики строковых выражений;поля для примитивных свойства класса модели включая свойства родительского класса.
Создается тип коллекции наборов свойств
_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
_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
_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
_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
_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
_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
_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
_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
_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
_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
_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— определяет команду разблокировки сущности.
Контроль значений BigDecimal#
Модуль выполняет проверку переданных в пакете команд значений типа BigDecimal на соответствие заданных в модели length и scale.
Детальное изложение в разделе документации Протокол JSON-RPC компонента DataSpace Core.
Использование 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;для категории идентификатора
AUTO(SNOWFLAKE) обязательно наличие уникального индекса.
Примечание
Для стратегии наследования
SINGLE_TABLEучитывается доступность индексов предков.
Отношение id и exist#
Параметр input команды заполняется по правилам create, т.е. использование атрибута id зависит от категории идентификатора.
Для категории AUTO_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- контекст мультипоиска, сериализованный в строку, пример контекста можно найти здесь
Пример: поля запроса для работы с продуктом.#
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— ключ идемпотентности пакета. Если аргумент не указан, то пакет не будет проверяться на идемпотентность.
Использование поля packet в мутации определяет границу транзакции в базе данных при выполнении формируемой мутации.
Тип объекта _Mutation в схеме:
type _Mutation {
packet(aggregateVersion: Long, idempotencePacketId: String): _Packet
}
Пример итоговой схемы#
Пример схемы 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
}
}
}
}
Пакет#
Для изменения данных используется пакет операций, которые выполняются в одной транзакции. В пакете могут выполняться операции по нескольким сущностям модели данных. Последовательность выполнения операций над объектами определяется порядком определения полей типа объекта _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
}
}
Другие примеры#
Работа с датами#
Поиск рейсов из Москвы во Владивосток между 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 в часовой зоне аэропорта.