gitverse new year логотип

dataspace_tanks_tutorial

Форк
0

2 месяца назад
9 дней назад
2 месяца назад
README.md

Учебное руководство по Platform V DataSpace на примере системы учета ГСМ (Нефтебаза)

Введение

Данное руководство является частью учебного курса по инструментам LowCode-платформы от СберТех - Platform V DataSpace & Flow. В нем на примере модели предметной области нефтебазы дается простое и понятное объяснение основных концепций предметно-ориентированного проектирования, работы с языком запросов GraphQL, а также специализированного инструмента DataSpace для описания бизнес-логики - строковых выражений. Руководство предназначено для широкой аудитории не требует каких-либо узкоспециализированных навыков и знания языков программирования. Однако для лучше понимания рекомендуется предварительно ознакомиться с материалом "Быстрый старт Platform V DataSpace".

Модель предметной области

Основные объекты модели:

  • Агрегат
  • Класс
  • Класс-справочник
  • Перечисление
  • Embeddable-класс
  • Абстрактный класс

Между объектами можно строить различные типы связей

Свойства (поля)

  • Обязательность
  • Версионирование

Способы генерации ID:

  • АВТО (SNOWFLAKE)
  • Смешанный (AUTO_ON_EMPTY)
  • Пользовательский

Скалярные типы данных:

  • String
  • Unicode String
  • Text
  • BigDecimal
  • Integer
  • Short
  • Long
  • Byte
  • Boolean
  • Character

Событийная модель:

  • наблюдатель
  • контейнер статусов
  • статус

Предметно-ориентированное проектирование

Получить расширенную информацию о, том что такое предметно-ориентированное проектирование (DDD) можно в разделе Дополнительная информация руководства "Быстрый старт Platform V DataSpace".

Создание проекта DataSpace

Далее будет пошагово рассматриваться созданием модели предметной области на примере учета ГСМ (Нефтебаза), готовую модель можно скачать по ссылке.

Агрегаты, классы, свойства

Агрегат - группа тесно связанных сущностей, с явно выделенным корнем.

Агрегат определяет транзакционную границу, в рамках одной транзакции мы можем работать только с одной сущностью, ее данными

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

Создание агрегата и корневого класса Tank

Свойства:

  • name String обязательный
  • currentVal Integer
  • location (тип будет указан позже)
  • tankType (тип будет указан позже)

Способ заполнения ID - SnowFlake

Создание embeddable-класса Coordinates

Embeddable-классы (встроенные классы) применяется для группировки нескольких полей примитивов в одно. В данном проекте встроенный класс Coordinates используется для хранение значений долготы и широты

Свойства:

  • latitude String широта
  • longitude String долгота

После создания встроенного класса Coordinates его можно использовать в качестве типа для полей других классов.

Укажем данный класс в качестве типа для поля location класса Tank:

Создание классов-справочников

Класс-справочник, в отличии от агрегата который является атомарной единицей и у которого должны быть "мягкие ссылки" и который можно перенести в другую БД, на значения КС можно ссылаться как на системную информацию

TankType (Типы цистерн)

  • name String
  • description String
  • maxVal Integer
  • measure (тип будет указан позже)

Measure (Единицы измерения)

  • name String
  • description String

Связываем перетаскиванием поле measure класса-справочника CisternalType с классом-справочником Measure Для поля tankType класса Tank типом указываем класс-справочник TankType

Добавление класса к агрегату Tank

Добавляем к агрегату Tank новый класс TankOperation

Тип связи - Композиция, класс-владелец Tank (необходимо выбрать, так как классов в агрегате может быть много) тип связи - один ко многим

В новом классе TankOperation будет новое свойство tank, а в классе Tank поле tankOperationList если переносить на базы данных это аналог внешнего ключа

!

Добавляем еще свойства TankOperation:

  • val Integer
  • operDateTimeUtc OffsetDateTime (чтобы не было проблем с часовыми поясами)
  • kind (тип будет указан позже)

Перечисление ENUM

Перечисления предназначены для хранения ограниченного набора значений (коллекции)

Cоздаем ENUM TankOperKind - тип операции

Значения:

  • INPUT
  • OUTPUT

для поля kind класса TankOperation указываем типом перечисление TankOperKind

Статусная модель

Каждая сущность может быть представлена разными статусами для разных наблюдателей Нефтебаза, операция для внешних пользователей (2-3 статуса) и для внутренних (+куча другой инфы)

Переименуем статус stausCode в NEW и добавим еще статусы:

  • CANCELED
  • APPRUVED
  • DONE

Успешная проверка модели перед выпуском:

Выпуск модели

Основы языка запросов GraphQL

GraphQL-конструктор DataSpace

После выпуска модели в интерфейсе DataSpace появляются три новых вкладки:

  • GraphQL конструктор - интерактивный playground для выполения GraphQL-запросов
  • Детали - информация о доступных API endpoints DataSpace
  • Разрешения - настройка параметров доступа для GraphQL-запросов

GraphQL конструктор

Вкладка SHEMA - просмотр и скачивание GraphQL-схемы модели предметной области

Вкладка DOCS - просмотр информации о доступных GraphQL-запросах операция + тип = имя доступные запросы или мутации:

Настройки GraphQL-конструктора:

Подсказки по коду: Ctrl + пробел

Типы данных GraphQL

  • скалярные (примитивные)
  • кастомные

Типы запрос GraphQL

  • Query - получение данных
  • Mutation (create, update, delete)
  • Subscription (подписки) - отслеживание изменений данных на оснвое Websocket

Простой запрос (query) - получение списка всех цистерн

query searchTank {
searchTank {
elems{
id
currentVal
location {
longitude
latitude
}
}
}
}

Результат:

{
"data": {
"searchTank": {
"elems": []
}
}
}

Мы получаем пустой массив elems так как в базе данных пока нет ни одной записи для сущности Tank

Мутации (mutation) - создание, обновление, удаление данных

Выполним две мутации для наполнения классов-справочников Measure и TankType

Пакеты

Мутации могут выполняться в составе пакета Пакет = транзакция базы данных Команды выполняются последовательно Если какая-то операция пакета на выполняется весь пакет откатывается транзакция завершается

В DataSpace есть два типа пакетов:

  • packet
  • dictionaryPacket

Мутация - Создание единицы измерения в классе-справочнике Measure

мутация updateOrCreateMeasure проверяет есть ли уже данная запись, если да - она обновляет ее, если нет - создает.

created - возвращает true если запись была создана returning позволяет вернуть свойства созданной сущности сразу после операции (доступны поля сущности)

mutation CreateMeasure {
dictionaryPacket {
updateOrCreateMeasure(
input: {
name: "M3",
description: "Метр кубический",
id: "m3" }
) {
created
returning{
id
}
}
}
}

Рузультат:

{
"data": {
"dictionaryPacket": {
"updateOrCreateMeasure": {
"created": true,
"returning": {
"id": "m3"
}
}
}
}
}

Алиасы (псевдонимы)

GraphQL позволяет создавать алиасы для пакетов и мутаций. Это бывает например необходимо когда мы хотим выполнить в одном пакете несколько одинаковых операций с одинакомыми именами. Без алиасов в ответе мы бы получали одинаковые имена в JSON поэтому GraphQL схлопывал бы их один. Алиасы позволяют этого избежать:

mutation CreateMeasure {
p1: dictionaryPacket {
t1:updateOrCreateMeasure(
input: {
name: "Т",
description: "Тонна",
id: "t" }
) {
created
returning{
id
name
description
}
}
t2:updateOrCreateMeasure(
input: {
name: "Г",
description: "Галлон",
id: "g" }
) {
created
returning{
id
name
description
}
}
}
}

Рузультат:

{
"data": {
"p1": {
"t1": {
"created": true,
"returning": {
"id": "t",
"name": "Т",
"description": "Тонна"
}
},
"t2": {
"created": true,
"returning": {
"id": "g",
"name": "Г",
"description": "Галлон"
}
}
}
}
}

Мутация - Добавление типа цистерны в класс-справочник TankType

mutation createTankType {
p1: dictionaryPacket {
smallTank: updateOrCreateTankType(
input: {
id: "smallTank"
name: "Малая цистерна"
measure: "m3"
maxVal: 1000
}
) {
created
}
}
p2: dictionaryPacket {
middleTank: updateOrCreateTankType(
input: {
id: "middleTank"
name: "Средняя цистерна"
measure: "m3"
maxVal: 3000
}
) {
created
}
}
p3: dictionaryPacket {
bigTank: updateOrCreateTankType(
input: {
id: "bigTank"
name: "Большая цистерна"
measure: "m3"
maxVal: 5000
}
) {
created
}
}
}

Результат:

{
"data": {
"p1": {
"smallTank": {
"created": true
}
},
"p2": {
"middleTank": {
"created": true
}
},
"p3": {
"bigTank": {
"created": true
}
}
}
}

Создание цистерны

Важно что используем уже packet, а не dictionaryPacket:

mutation createTank {
packet{
createTank(input:{
name:"Цистерна 1"
tankType:"smallTank"
currentVal:0
})
{
id
}
}
}

Результат запроса:

{
"data": {
"packet": {
"createTank": {
"id": "7426871612607692801"
}
}
}
}

7426871612607692801 - идентификатор цистерны, сгенерированный методом SNOWFLAKE

Переменные в GraphQL-запросах

GraphQL позволяет использовать в запросах и мутациях параметры. Это дает возможность передавать данные в контекст запросов из внешних систем. Значения параметров передаются во вкладке Query variables GraphQL-конструктора

Мутация - создание цистерны с использованием переменных

mutation createTank (
$name: String!
$tankType: ID!
){
packet{
createTank(input:{
name:$name
tankType:$tankType
currentVal:0
})
{
id
}
}
}

Query variables:

{
"name": "Цистерна 2",
"tankType": "middleTank"
}

Результат запроса:

{
"data": {
"packet": {
"createTank": {
"id": "7426873618357420033"
}
}
}
}

Query-запрос - получение списка цистерн

query searchTank {
searchTank {
elems{
id
name
currentVal
}
}
}

У запросов в DataSpace могут быть дополнительные параметры:

  • cond - условие (аналого WHERE SQL)

  • limit - ограничение количества элементов в результате запроса

  • offset - пропустить указанное поличество элементов перед началом вывода

  • sort - сортировка (ASC, DESC)

  • cond: String — условие фильтрации в грамматике строковых выражений;

  • limit: Int — ограничение на количество элементов;

  • offset: Int — смещение;

  • sort: [_SortCriterionSpecification!] — сортировка;

Строковые выражения DataSpace

Для cond могут применяются cтроковые выражения - специальный синтаксис DataSpace для описания различных параметров поиска, который удобно использовать для описания сложной бизнес-логики без необходимости описания ее в backend

it - текущий элемент по которому итерируемся, аналог this id - какое-либо поле, например id

Примеры:

Поиск цистерны по определенному id

query searchTank {
searchTank (cond: "it.id == '7426871612607692801'") {
elems{
id
name
currentVal
}
}
}

Поиск цистерны определенного типа

query searchTank {
searchTank (cond: "it.tankType.id == 'smallTank'") {
elems{
id
name
}
}
}

Получение всех типов цистерн, у которых единицей измерения объема является 'l'

query searchTankType {
searchTankType (cond:"it.measure.id == 'l'") {
elems {
id
description
maxVal
measure{
id
description
}
}
}
}

Получение всех цистерн, у которых единицей измерения является "m3" Условие с использованием свойства вложенного объекта

query searchTank {
searchTank (cond:"it.tankType.measure.id == 'm3'") {
elems{
id
name
}
}
}

Дополнительные примеры использования строковых выражений:

использование < > == != операций

cond:"it.maxVal > 5000"

условие "Отрицание":

!${условие} (например, !it.services.$exists);

логическое "И":

${условие} && ${условие} (например, it.services.$exists && it.product != null);

логическое "ИЛИ":

${условие} || ${условие} (например, it.services.$exists || it.product != null).

Использование методов строковых выражений, условие: код продукта равен 'product1' независимо от регистра:

it.code.$lower == 'product1'

Использование методов строковых выражений, условие: суммарное время выполнение всех сервисов продукта, код которых начинается с кода продукта, не превышает 10 (при поиске продуктов).

it.services{cond = it.code $like root.code + '%'}.executionTime.$sum <= 10

Условие: код продукта равен одному из значений 'product1', 'product2' или 'product3'.

it.code == any(['product1', 'product2', 'product3])

Комплексный пример по строковым выражениям

Показывает гибкие возможности построение запросов DataSpace. Запрос возвращает список цистерн, у которых значение параметра tankType.measure.id начинается с литеры "M" вне зависимости от регистра.

При этом также задействованы параметры offset (пагинация), limit (ограничение количества элементов в выводе) и sort (сортировка результатов по определенному полю, в данном случае name)

Также важно отметить конструкцию @strExpr (директива), позволяющую избежать проблем с обработкой GraphQL-запроса, когда переменная $measureSearchStr используется только в строковом выражении.

# Write your query or mutation here
query searchTank (
$measureSearchStr: String
$offset: Int
$limit: Int
) {
searchTank (
cond:"it.tankType.measure.id.$upper $like ${measureSearchStr}.$upper + '%'"
offset:$offset
limit:$limit
sort: {crit: "it.name", order:DESC}
)
@strExpr(string:$measureSearchStr)
{
elems{
id
name
}
}
}
{
"measureSearchStr": "M",
"offset": 1,
"limit": 2
}
{ "data": { "searchTank": { "elems": [ { "id": "7426874138048462849", "name": "Цистерна 2" }, { "id": "7426871612607692801", "name": "Цистерна 1" } ] } } }

Мутация - изменение количества топлива в цистерне

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

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

Отдельно стоит указать параметр lock:WAIT - он позволяет выполнять блокировку, дает возможность бесконфликтно выполнять множественные изменения данных, обеспечивая их очередность.

mutation decreaseTankVolume(
$tankId: ID!
$val: BigDecimal!
$opDate: _OffsetDateTime!
) {
packet {
getTank(
id: "find: it.id == ${tankId} && it.currentVal-${val}-it.tankType.minVal>=0"
failOnEmpty: true
lock: WAIT
) {
id
}
updateTank(
input: { id: $tankId }
inc: { currentVal: { value: $val, negative: true } }
) {
id
currentVal
}
createTankOperation(
input: {
tank: $tankId
val: $val
transfer: { entityId: "ttt" }
kind: OUTPUT
operDateTimeUtc: $opDate
}
) {
id
operDateTimeUtc
}
}
}

Идемпотентность

Для обеспечения идемпотентного вызова пакета необходимо указать атрибут idempotencePacketId поля packet

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

Ссылки

Platform V DataSpace:
https://platformv.sbertech.ru/products/instrumenty-razrabotchika/dataspace

Строковые выражения DataSpace:
https://client.sbertech.ru/docs/public/APT/1.13.0/DSPC/1.13.0/documents/string-expressions/index.html

Протокол GraphQL:
https://client.sbertech.ru/docs/public/APT/1.13.0/DSPC/1.13.0/documents/dataspace-core-graphql-protocol/index.html

Канал Platform V на Rutube:
https://rutube.ru/channel/30199350/

Инструменты Сбера для разработчиков:
https://developers.sber.ru/

TG-канал Sber Developer News:
https://t.me/SberDeveloperNews

TG-группа developers.sber.ru:
https://t.me/smartmarket_community

Habr:
https://habr.com/ru/companies/sberbank/

Благодарим за внимание! Успехов!

Описание

Учебное руководство по Platform V DataSpace на примере системы учета ГСМ (Нефтебаза)

https://platformv.sbertech.ru/products/instrumenty-razrabotchika/dataspace

Сообщить о нарушении

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.