7. Стейт-машина СМЭВ QL
СМЭВ QL содержит встроенную машину состояний для изменения объектов модели внутри витрин данных. Одновременно с этим Стейт машина может, в качестве подтверждения перехода состояния, использовать внешний источник (например ИС Электронной очереди).
7.1. Конфигурирование Стейт-машины
Карта состояний и переходов описывается в формате YAML-файла state.yaml располагаемого в папке states/<имя-модели>/<х.х версия модели>
инстанса СМЭВ QL Сервера.
Описание формата и правил карты состояний:
model: slot # имя модели
states: # массив состояний объекта
- state: available # название состояния available
attributes: # массив атрибутов, описывающих состояние
- name: status # состояние определятся значением атрибута status
value: AVAILABLE # значение атрибута для описываемого состояния
initial: true
- state: booked
attributes:
- name: status
value: RECORDED
- state: reserved
attributes:
- name: status
value: RESERVED
- state: cancelled
attributes:
- name: status
value: CANCELED
- state: blocked
attributes:
- name: status
value: BLOCKED
events: # список событий изменения состояний, из них создаются методы API
- event: book # создает метод POST /states/slot/book
from: # массив состояний из которых возможен вызов события
- available
- reserved
to: booked # в какое состояние переводится объект после события
hooks: # массив связанных событий
- model: book # после перевода надо вызвать событие init для модели book
event: init
confirm:
source: rmis_rest # названия источника
endpoint: /booking/book
method: post
body: payload # что включать в тело запроса (full|state|conditions|payload|credentials)
accept: # условие принятия
jsonpath: $.status.statusCode
eq: 0 # ожидаем, что statusCode будет равен 0
- event: reserve
from: available
to: reserved
- event: block
from: available
to: blocked
- event: cancel
from:
- available
- reserved
- booked
- blocked
to: cancelled
hooks:
- model: book
event: cancel
7.2. Удаление записи через Стейт-машину
Статус удаления отмечается флагом delete: true, соответствует удалению записи по conditions. Значения атрибутов для такого
статуса не применимы, при наличи атрибутов необходимо выдать предупреждение в лог и проигнорировать их.
Статус с признаком delete конечный по смыслу в графе состояний, он не может использоваться в разделе from любых событий.
Проверок на эту тему не делаем, даже если кто то и пропишет из него переход, то условие никогда не выполнится.
7.3. Передача данных без изменения статуса (статичный ивент)
Можно передавать данные к связанному источнику из блока confirm с использованием флага статичного запроса при создании
стейта: static: true.
Созданный с указанием стейта с флагом static: true ивент не меняет статус модели, а только передает указанную в описании
ивента часть запроса в блоке confirm к связанному источнику.
Пример state ивента:
model: slot # имя модели
states: # массив состояний объекта
- state: static
static: true
events: # список событий изменения состояний, из них создаются методы API
- event: state
from:
- available
- reserved
- booked
- blocked
- cancelled
- deleted
to: static # в какое состояние переводится объект после события
confirm:
source: rmis_rest # названия источника
endpoint: /booking/book
method: post
body: payload # что включать в тело запроса (full|state|conditions|payload|credentials)
accept: # условие принятия
jsonpath: $.status.statusCode
eq: 0 # ожидаем, что statusCode будет равен 0
7.4. Обновление объектов через Стейт-машину
Через Стейт-машину можно обновлять записи в витрине. Для этого при конфигурировании карты состояний необходимо задать значение у
event updatable: true. При этом если требуется дать возможность обновлять только часть атрибутов, то ограничить этот список можно
перечислив атрибуты в массиве ``updatable_attribute``s.
- event: reserve
from: available
to: reserved
updatable: true // по умолчанию false для всех, кроме init, возможность изменять запись при переводе статуса
updatable_attributes: [] // массив атрибутов, которые можно обновлять, пустой — можно все
Данные для обновления будут браться из блока payload запроса на смену состояния. Для события init такое конфигурирование не
требуется — по умолчанию обновления разрешены для всех атрибутов из payload.
7.5. Обогащение payload запроса дополнительными атрибутами
Можно обогащать payload запроса к Витрине. Для этого необходимо выставить в блоке enrich раздела confim значение
allow: true, с указанием jsonpath для получения атрибутов для обогащения.
Пример ивента с блоком enrich:
events: # список событий изменения состояний, из них создаются методы API
- event: book # создает метод POST /states/slot/book
from: # массив состояний из которых возможен вызов события
- available
- reserved
to: booked # в какое состояние переводится объект после события
hooks: # массив связанных событий
- model: book # после перевода надо вызвать событие init для модели book
event: init
confirm:
source: rmis_rest # названия источника
endpoint: /booking/book
method: post
body: payload # что включать в тело запроса (full|state|conditions|payload|credentials)
accept: # условие принятия
jsonpath: $.status.statusCode
eq: 0 # ожидаем, что statusCode будет равен 0
enrich:
allow: true // по умолчанию false
attributes: [] // пустой массив — все, а если перечислены, то только эти
jsonpath: $.update.payload // ожидается что по этому адресу объект с атрибутами и значениями
При выставлении атрибута allow: true в блоке enrich, при выполнении запроса блока confirm происходит обращение к объекту,
указанному в переменной jsonpath с возвратом атрибутов, перечисленных в массиве atttributes (пустой массив означает отсутствие
ограничений). Извлеченные атрибуты возвращаются в payload ответа confirm запроса. Пример возможного confirm ответа от ИС
приведен ниже:
{
"status": "OK",
"update": {
"payload": {
"position": "Тест1",
"rvr_cv_id": "8888888-AEA2-4EF7-95C4-B29A4A5E990",
"rvr_candidate_id": "99999999-AEA2-4EF7-95C4-B29A4A5E990"
}
}
}
Атрибуты payload ответа confirm запроса обогащают payload основного запроса. Обновление данных в витрине в случае
обогащения payload дополнительными атрибутами происходит независимо от значения updatable.
При отсутствии объекта по пути jsonpath в confirm запросе возвращается ошибка 501 "Отсутствует объект при обращении по jsonpath",
выполнение запроса прерывается.
При несоответствии атрибутов в блоке attributes и в jsonpath - в payload вернутся только те атрибуты, по которым есть
соответствия с фиксацией в логах событий уровня WARN о несоответствующих атрибутах:
"Отсутствует атрибут *attribute_name" в $.update.payload".
В случае, если в payload присутствуют атрибуты, аналогичные тем, что возвращаются при выполнении блока enrich, обновляются
данные в соответствии со значениями вернувшимися при выполнении блока enrich. В этом случае в логах фиксируется запись "Изменение
значений атрибутов: *attribute_name" payload в соответствии со значениями блока enrich".
7.6. Методы API Стейт-машины
При наличии заполненных состояний машины СМЭВ QL Сервер генерирует API c набором HTTP-методов, отвечающих за изменение и просмотр состояний объектов:
GET /states— получить карту переходов;GET /states/<model-name>— получить карту переходов конкретной модели;POST /states/<model-name>/<event-name>— выполнить переход состояний для модели.
Запрос выполнения перехода:
POST /states/slot/book
{
"state": {
"conditions": {
"id": "d9e70331-b4c0-4e96-96b6-322ac75e5188" # slot_id
},
"payload": {
"bookId":"82dcac12-0a29-4fff-b9a7-8dfc84f7853d",
"patient_Id":"23453456",
"booking_type":"APPOINTMENT",
"caseNumber":"73367196",
"preliminaryReservation": false,
"email":"email@gmail.com",
"mobilePhone":"89150000102",
"referral_id":"102111",
"cards_id":"102"
}
},
"credentials": {
"system": {
"mnemonic": "117bed7f-1c07-4079-a446-1161588db4e5",
"instance_id": "ccb4a88f-f44b-43e7-8a97-3e45c8345e90",
"user_id": "5ed38461-0907-486a-930a-7b443482932c"
},
"request": {
"id": "df5a0073-c6be-4d8c-8eb2-9b2f4188a429",
"sub_id": "0cdb59ce-224b-4118-8da1-c5ea08a5d955",
"name": "request_name",
"purpose_id": "ed1170f1-3caa-4985-aa38-c9c5a190b770",
"audit": false,
"audit_id": "fc1048fe-323d-4eeb-92df-5710b3d1d100",
"audit_token": false
}
}
}
7.7. Спецификация интерфейса Стейт-машины
openapi: 3.0.0
x-stoplight:
id: 5i2oag6m5eq3v
info:
title: SmevQLStateMachine
version: '1.0'
description: ''
servers:
- url: 'http://localhost:3000'
paths:
/states:
parameters: []
get:
summary: Get models
tags: []
responses:
'200':
description: ''
content:
application/x-yaml:
schema:
$ref: '#/components/schemas/Models'
examples: {}
application/xml:
schema:
type: object
properties: {}
multipart/form-data:
schema:
type: object
properties: {}
text/html:
schema:
type: object
properties: {}
operationId: get-models
description: Retrieve the information of models
'/states/{model}':
parameters:
- schema:
type: string
name: model
in: path
required: true
get:
summary: Get model
tags: []
responses:
'200':
description: Model Found
content:
application/x-yaml:
schema:
$ref: '#/components/schemas/Model'
examples: {}
'400':
description: Bad Request
'404':
description: Model Not Found
operationId: get-model
description: Retrieve the information of model
parameters: []
'/states/{model}/{event}':
post:
summary: State change
operationId: post-state
responses:
'200':
description: State Updated
content:
plain/text:
schema:
type: string
examples: {}
'400':
description: Bad request
'404':
description: Not Found
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/StateUpdate'
examples: {}
description: Post the necessary fields for the API to create a new user.
description: Update state
parameters: []
parameters:
- schema:
type: string
name: model
in: path
required: true
- schema:
type: string
name: event
in: path
required: true
components:
schemas:
State:
type: object
x-stoplight:
id: 89f55561cae04
properties:
state:
type: string
attributes:
type: array
minItems: 1
items:
$ref: '#/components/schemas/Attribute'
initial:
type: boolean
Model:
type: object
x-stoplight:
id: b96a73db1e1b2
properties:
model:
type: string
states:
type: array
minItems: 1
items:
$ref: '#/components/schemas/State'
events:
type: array
items:
$ref: '#/components/schemas/Event'
Events:
title: Events
x-stoplight:
id: qdvbkmgs9xkli
type: array
items:
$ref: '#/components/schemas/Event'
States:
$ref: '#/components/schemas/State'
x-stoplight:
id: wab1w6ro30nrl
Event:
title: Event
x-stoplight:
id: sr024y2v7khum
type: object
properties:
event:
type: string
from:
type: string
to:
type: string
Models:
title: ModelSet
x-stoplight:
id: e7de590d788a7
type: array
items:
$ref: '#/components/schemas/ModelInstance'
ModelInstance:
type: object
properties:
model:
type: string
states:
$ref: '#/components/schemas/States'
events:
$ref: '#/components/schemas/Events'
ModelSet:
type: array
items:
$ref: '#/components/schemas/Model'
Attribute:
title: Attribute
x-stoplight:
id: 3kiqu047734tc
type: object
properties:
name:
type: string
value:
type: string
description: ''
StateUpdate:
title: StateUpdate
x-stoplight:
id: 2iyfifo0q1dt2
type: object
properties:
state:
type: object
properties:
conditions:
type: object
payload:
type: object
credentials:
type: object
properties:
system:
type: object
properties:
mnemonic:
type: string
instance_id:
type: string
user_id:
type: string
request:
type: object
properties:
id:
type: string
format: uuid
sub_id:
type: string
format: uuid
name:
type: string
purpose_id:
type: string
format: uuid
audit:
type: boolean
audit_id:
type: string
format: uuid
audit_token:
type: string
Пример реализации: Спецификация интерфейса Стейт-машины РМИС (OpenAPI)
openapi: 3.0.0
x-stoplight:
id: 5i2oag6m5eq3v
info:
title: SmevQLStateMachine
version: '1.0'
description: ''
servers:
- url: 'http://localhost:3000'
paths:
/states:
parameters: []
get:
summary: Get models
tags: []
responses:
'200':
description: ''
content:
application/x-yaml:
schema:
$ref: '#/components/schemas/Models'
examples: {}
application/xml:
schema:
type: object
properties: {}
multipart/form-data:
schema:
type: object
properties: {}
text/html:
schema:
type: object
properties: {}
operationId: get-models
description: Retrieve the information of models
'/states/{model}':
parameters:
- schema:
type: string
name: model
in: path
required: true
get:
summary: Get model
tags: []
responses:
'200':
description: Model Found
content:
application/x-yaml:
schema:
$ref: '#/components/schemas/Model'
examples: {}
'400':
description: Bad Request
'404':
description: Model Not Found
operationId: get-model
description: Retrieve the information of model
parameters: []
'/states/{model}/{event}':
post:
summary: State change
operationId: post-state
responses:
'200':
description: State Updated
content:
plain/text:
schema:
type: string
examples: {}
'400':
description: Bad request
'404':
description: Not Found
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/StateUpdate'
examples: {}
description: Post the necessary fields for the API to create a new user.
description: Update state
parameters: []
parameters:
- schema:
type: string
name: model
in: path
required: true
- schema:
type: string
name: event
in: path
required: true
components:
schemas:
State:
type: object
x-stoplight:
id: 89f55561cae04
properties:
state:
type: string
attributes:
type: array
minItems: 1
items:
$ref: '#/components/schemas/Attribute'
initial:
type: boolean
Model:
type: object
x-stoplight:
id: b96a73db1e1b2
properties:
model:
type: string
states:
type: array
minItems: 1
items:
$ref: '#/components/schemas/State'
events:
type: array
items:
$ref: '#/components/schemas/Event'
Events:
title: Events
x-stoplight:
id: qdvbkmgs9xkli
type: array
items:
$ref: '#/components/schemas/Event'
States:
$ref: '#/components/schemas/State'
x-stoplight:
id: wab1w6ro30nrl
Event:
title: Event
x-stoplight:
id: sr024y2v7khum
type: object
properties:
event:
type: string
from:
type: string
to:
type: string
Models:
title: ModelSet
x-stoplight:
id: e7de590d788a7
type: array
items:
$ref: '#/components/schemas/ModelInstance'
ModelInstance:
type: object
properties:
model:
type: string
states:
$ref: '#/components/schemas/States'
events:
$ref: '#/components/schemas/Events'
ModelSet:
type: array
items:
$ref: '#/components/schemas/Model'
Attribute:
title: Attribute
x-stoplight:
id: 3kiqu047734tc
type: object
properties:
name:
type: string
value:
type: string
description: ''
StateUpdate:
title: StateUpdate
x-stoplight:
id: 2iyfifo0q1dt2
type: object
properties:
state:
type: object
properties:
conditions:
type: object
properties:
id:
type: string
format: uuid
payload:
type: object
properties:
book_id:
type: string
format: uuid
patient_id:
type: string
booking_type:
type: string
case_number:
type: string
preliminary_reservation:
type: boolean
email:
type: string
mobile_phone:
type: string
referral_id:
type: string
cards_id:
type: string
credentials:
type: object
properties:
system:
type: object
properties:
mnemonic:
type: string
instance_id:
type: string
user_id:
type: string
request:
type: object
properties:
id:
type: string
format: uuid
sub_id:
type: string
format: uuid
name:
type: string
purpose_id:
type: string
format: uuid
audit:
type: boolean
audit_id:
type: string
format: uuid
audit_token:
type: string
7.7.1. Выполнение операций обновления данных в витрине
Инсерты в витрину выполняются в порядке поступления запросов.
Каждый экземпляр СМЭВ QL сервера ведет нарастающий счетчик инсертов.
Каждый экземпляр СМЭВ QL сервера создает один поток управления дельтами, который выполняет:
Периодически (период конфигурируемая величина, по умолчанию 60 сек) проверяет значение счетчика числа инсертов, если значение счетчика более 0
Выполняет открытие и закрытие дельты с флагом immediate, ошибки открытия и закрытия дельты игнорируются. Попытка закрытия дельты выполняется независимо от успешности открытия дельты.
Обнуляет значение счетчика.
Периодически (период конфигурируемая величина, по умолчанию 30 мин)
Выполняет открытие и закрытие дельты с флагом immediate, ошибки открытия и закрытия дельты игнорируются. Попытка закрытия дельты выполняется независимо от успешности открытия дельты.
Чтение данных сервером СМЭВQL выполняется с применением AS OF <maxLong>.
7.7.2. Обновление объектов через Стейт-машину
Через Стейт-машину можно обновлять записи в витрине. Для этого при конфигурировании карты состояний необходимо
задать значение у event updatable: true.
При этом если требуется дать возможность обновлять только часть
атрибутов, то ограничить этот список можно перечислив атрибуты в массиве updatable_attributes.
- event: reserve
from: available
to: reserved
updatable: true // по умолчанию false для всех, кроме init, возможность изменять запись при переводе статуса
updatable_attributes: [] // массив атрибутов, которые можно обновлять, пустой — можно все
Данные для обновления будут браться из блока payload запроса на смену состояния.
Для события init такое конфигурирование не требуется - по умолчанию обновления разрешены для всех атрибутов из payload.