9. Стейт-машина СМЭВ QL

СМЭВ QL содержит встроенную машину состояний для изменения объектов модели внутри витрин данных. Одновременно с этим Стейт машина может, в качестве подтверждения перехода состояния, использовать внешний источник (например ИС Электронной очереди).

9.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

9.2. Удаление записи через Стейт-машину

Статус удаления отмечается флагом delete: true, соответствует удалению записи по conditions. Значения атрибутов для такого статуса не применимы, при наличи атрибутов необходимо выдать предупреждение в лог и проигнорировать их.

Статус с признаком delete конечный по смыслу в графе состояний, он не может использоваться в разделе from любых событий. Проверок на эту тему не делаем, даже если кто то и пропишет из него переход, то условие никогда не выполнится.

9.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

9.4. Обновление объектов через Стейт-машину

Через Стейт-машину можно обновлять записи в витрине. Для этого при конфигурировании карты состояний необходимо задать значение у event updatable: true. При этом если требуется дать возможность обновлять только часть атрибутов, то ограничить этот список можно перечислив атрибуты в массиве ``updatable_attribute``s.

- event: reserve
  from: available
  to: reserved
  updatable: true // по умолчанию false для всех, кроме init, возможность изменять запись при переводе статуса
  updatable_attributes: [] // массив атрибутов, которые можно обновлять, пустой — можно все

Данные для обновления будут браться из блока payload запроса на смену состояния. Для события init такое конфигурирование не требуется — по умолчанию обновления разрешены для всех атрибутов из payload.

9.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".

9.6. Методы API Стейт-машины

При наличии заполненных состояний машины СМЭВ QL Сервер генерирует API c набором HTTP-методов, отвечающих за изменение и просмотр состояний объектов:

  1. GET /states — получить карту переходов;

  2. GET /states/<model-name> — получить карту переходов конкретной модели;

  3. 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
        }
    }
}

9.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

9.7.1. Выполнение операций обновления данных в витрине

Инсерты в витрину выполняются в порядке поступления запросов.

Каждый экземпляр СМЭВ QL сервера ведет нарастающий счетчик инсертов.

Каждый экземпляр СМЭВ QL сервера создает один поток управления дельтами, который выполняет:

  1. Периодически (период конфигурируемая величина, по умолчанию 60 сек) проверяет значение счетчика числа инсертов, если значение счетчика более 0

  • Выполняет открытие и закрытие дельты с флагом immediate, ошибки открытия и закрытия дельты игнорируются. Попытка закрытия дельты выполняется независимо от успешности открытия дельты.

  • Обнуляет значение счетчика.

  1. Периодически (период конфигурируемая величина, по умолчанию 30 мин)

  • Выполняет открытие и закрытие дельты с флагом immediate, ошибки открытия и закрытия дельты игнорируются. Попытка закрытия дельты выполняется независимо от успешности открытия дельты.

Чтение данных сервером СМЭВQL выполняется с применением AS OF <maxLong>.

9.7.2. Обновление объектов через Стейт-машину

Через Стейт-машину можно обновлять записи в витрине. Для этого при конфигурировании карты состояний необходимо задать значение у event updatable: true.

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

- event: reserve
  from: available
  to: reserved
  updatable: true // по умолчанию false для всех, кроме init, возможность изменять запись при переводе статуса
  updatable_attributes: [] // массив атрибутов, которые можно обновлять, пустой — можно все

Данные для обновления будут браться из блока payload запроса на смену состояния.

Для события init такое конфигурирование не требуется - по умолчанию обновления разрешены для всех атрибутов из payload.