Skip to content

Архитектура хранилища BPMN ​

На этой странице описаны состояние панелей BPMN, поток данных, обнаружение изменений и сохранение в бэкенде.

Обзор ​

Pinia-хранилище useBpmnPanelsStore управляет состоянием всех панелей BPMN и связывает три слоя:

  • интерфейс — рендерер @mes/bpmn;
  • API бэкенда — запуск метода для получения графа и операции сохранения;
  • модули действий — отдельные обработчики создания, обновления и удаления.

Устройство хранилища ​

Состояние панели ​

Для каждой панели хранится отдельный объект:

typescript
type PanelState = {
  loading: boolean; // Выполняется запрос к API
  response: GraphStateResponse; // Исходный трёхуровневый ответ бэкенда
  graphContextObjectGuids?: string[]; // ресурсы Object подтверждённого графа
  error: unknown | null; // Последняя ошибка
  lastModel?: BpmnRendererInput; // Снимок для обнаружения изменений
  newNodeData?: {
    // Временные данные при создании узла
    tempId: string;
    position: { x: number; y: number };
  };
  lastCreatedNodeGuid?: string; // GUID последнего созданного узла
  viewbox?: {
    // Сохранённое положение холста
    x: number;
    y: number;
    zoom: number;
  };
};

graphContextObjectGuids принадлежит ответу, который успешно вернул метод графа. Поиск через getGraphContextLink разрешён только для одного неизменного Object из этого подтверждённого контекста.

Вычисляемые значения ​

bpmnJsonModel(panelId) ​

Возвращает модель BPMN для @mes/bpmn.

  • Делегирует преобразование composable-функции useBpmnRendererInput.
  • Преобразует исходный response в BpmnRendererInput.
  • Реактивно обновляется вместе с ответом.
  • Возвращает undefined, если ответа ещё нет.

lastModel(panelId) ​

Возвращает последний подтверждённый снимок модели.

  • Сравнение с текущей моделью показывает, что изменил пользователь.
  • Значение обновляется после fetchData() и после принятых изменений.
  • Снимок участвует в обнаружении перемещённых узлов, новых или удалённых сущностей.

Действия ​

ensurePanelState(panelId) ​

Создаёт начальное состояние панели, если его ещё нет. Повторный вызов ничего не меняет и защищает остальные действия от обращения к отсутствующей записи.

cleanupPanelTransientState(panelId) ​

При размонтировании компонента удаляет временные данные, но сохраняет viewbox. Очищаются response, признаки загрузки и ошибки, данные создания и lastModel.

setLastModel({ panelId, model }) ​

Запоминает текущую модель как основу следующего сравнения. Вызывается после успешной загрузки графа и принятого изменения.

fetchData({ panelId }) ​

Получает граф BPMN через запуск метода.

  1. Проверяет конфигурацию панели и запроса.
  2. Разрешает GUID ресурсов Object из конфигурации или события выбора.
  3. Формирует MethodRunRequest.
  4. Отправляет POST /MetaMethods/Run.
  5. Сохраняет ответ и точный набор использованных GUID в состоянии панели.
  6. Строит lastModel из вычисленной модели.

Непустой panel.request.objects имеет приоритет. Если массив пуст и указан getSelectedObjectForEventId, используется Object из этого события. Ложные значения отбрасываются. graphContextObjectGuids обновляется только вместе с успешно полученным ответом.

applyBpmnChanges({ panelId, previous, current }) ​

Обнаруживает и сохраняет изменения графа.

  1. Фиксирует подтверждённый Object графа.
  2. Сравнивает previous и current.
  3. Перед и после каждого контекстного поиска, а также непосредственно перед POST /Link, проверяет, что Object графа и состояние загрузки не изменились.
  4. processBpmnChanges() создаёт типизированные события.
  5. persistBpmnGraphChange() передаёт события их обработчикам.
  6. После запроса на создание Link или любой ошибки координатор снова вызывает настроенный метод получения графа и использует его ответ.
  7. Для надёжного подтверждения Link хранилище проверяет наличие каждого созданного UUID среди идентификаторов загруженных рёбер.
  8. Хранилище показывает итоговое уведомление, а компонент принимает только обычный исход accepted; при сверке или ошибке виджет сбрасывается.

Поддерживаемые события:

  • update:nodesPositions — изменение координат узлов;
  • create:nodes — создание узлов;
  • delete:nodes — удаление узлов;
  • create:edges — создание рёбер;
  • delete:edges — удаление рёбер.

Если пользователь переносит начало или конец ребра, которое уже было в предыдущей модели, processBpmnChanges() отклоняет изменение до PUT /Link. Координатор снова вызывает метод получения графа, и полученная модель отменяет перенос. Это правило действует для каждого сохранённого ребра. Чтобы изменить конец, удалите коннектор и создайте его заново. Ограничение нужно потому, что повторно полученное ребро не содержит исходных данных, по которым можно отличить конец-Object от конца-Link.

Порядок обработки:

  1. удаление узлов и рёбер;
  2. обновление координат;
  3. создание рёбер и узлов.

Удаление выполняется раньше создания для сохранения ссылочной целостности.

Возможные исходы сохранения:

  • accepted — запись выполнена без обязательной сверки; текущая модель становится следующей основой сравнения;
  • reconciled — после ошибки метод графа вернул модель либо после создания вернул каждый Link из надёжного подтверждения; компонент сбрасывает виджет к этой модели;
  • persisted-unreconciled — POST /Link вернул по одному корректному UUID на запрос, но загрузка не удалась или не содержала один из этих UUID; интерфейс показывает известные GUID и запрещает рисовать коннектор снова, потому что Link уже создан. Пользователь проверяет Link по GUID в интерфейсе редактирования связей или прикладывает GUID и текст уведомления к обращению;
  • commit-unknown — подтверждение отсутствует, повреждено, содержит повторы либо ответ на отправленный запрос потерян, а повторный вызов метода графа не показал, какая запись завершилась. Приложение не выдумывает GUID и запрещает рисовать коннектор снова, пока пользователь не проверит коннектор между исходным и целевым узлами и ожидаемый Link в интерфейсе редактирования связей. Если такая проверка недоступна, пользователь прикладывает к обращению текст уведомления и полный блок request из конфигурации панели. По нему видно request.linkName и наличие либо отсутствие необязательных полей request.source и request.target;
  • failed — запись не подтверждена, и метод графа также завершился ошибкой.

requestDeleteNodes({ panelId, bpmnNodes }) ​

Передаёт удаление узлов в одноимённый модуль действия. Действие доступно и внешним вызывающим сторонам, например диалогу подтверждения.

Поток данных ​

Первая загрузка ​

text
1. Компонент монтируется
   ↓
2. fetchData({ panelId })
   ↓
3. POST /MetaMethods/Run с parent, method, objects и params
   ↓
4. Бэкенд возвращает GraphStateResponse
   ↓
5. Хранилище сохраняет response и graphContextObjectGuids
   ↓
6. useBpmnRendererInput преобразует ответ в BpmnRendererInput
   ↓
7. setLastModel() сохраняет снимок
   ↓
8. Компонент передаёт модель в @mes/bpmn

Обнаружение и сохранение изменений ​

text
1. Пользователь перемещает, создаёт, соединяет или удаляет элемент
   ↓
2. @mes/bpmn отправляет событие bpmn:changed
   ↓
3. Компонент вызывает applyBpmnChanges(previous, current)
   ↓
4. processBpmnChanges() строит типизированные события
   ↓
5. Контекстные концы проверяют Object графа вокруг поиска и перед записью
   ↓
6. События передаются в соответствующие модули действий
   ↓
7. Модули вызывают API бэкенда
   ↓
8. persistBpmnGraphChange() при необходимости вызывает настроенный метод графа
   ↓
9. Надёжные UUID созданных Link должны присутствовать в загруженных рёбрах
   ↓
10. Хранилище показывает итог, компонент принимает или сбрасывает модель

Точки интеграции ​

useBpmnRendererInput(panelId) ​

Файл: ../composables/use-bpmn-renderer-input.ts.

  • Преобразует GraphStateResponse в BpmnRendererInput.
  • Извлекает узлы и рёбра из вложенного ответа.
  • Сопоставляет ресурсы Object с видами узлов BPMN.
  • Собирает карточки и их поля.
  • Возвращает реактивную вычисляемую модель.

Модули действий ​

Каталог: ./actions/.

Каждый модуль владеет одной границей транспорта: принимает типизированные данные, формирует запрос и выполняет его через $fetchApi. Общая сверка и уведомления принадлежат хранилищу и persistBpmnGraphChange(); успешное создание Link не показывается внутри модуля действия.

updateNodesPositions(payload) ​

Обновляет атрибуты x_pos и y_pos перемещённых узлов.

requestCreateEdges(payload) ​

Создаёт ресурсы Link с разрешёнными концами sourceGuid и targetGuid, которые могут указывать на Object или Link. Подтверждение считается надёжным, только если ответ содержит по одному различному UUID на каждый запрос. Успех показывается только после того, как настроенный метод графа вернул модель рёбер с каждым таким UUID.

requestDeleteEdges(edgeGuids) ​

Удаляет ресурсы Link по GUID.

requestCreateNodes({ panelId, bpmnNodes }) ​

Создаёт ресурсы Object для узлов вместе с координатами. Модуль находит элемент палитры по node.data._bpmnTaskId, выполняет настроенные действия onObjectCreate, поддерживает editor.mode = "direct-request" и заменяет временный GUID.

requestDeleteNodes({ panelId, bpmnNodes }) ​

После подтверждения удаляет ресурсы Object для узлов и заново получает граф.

requestUpdateEdges(payload) ​

Низкоуровневое транспортное действие существует, но пользовательский путь BPMN не вызывает его для сохранённых рёбер: безопасное происхождение концов неизвестно.

Вспомогательные функции ​

processBpmnChanges(previous, current) ​

Файл: ./utils/process-bpmn-changes.ts.

  • Сравнивает две модели через just-diff.
  • Разделяет изменения на типизированные события.
  • Отклоняет смешанные стратегии одного конца до запроса.
  • Отклоняет каждое переподключение и требует удалить и заново создать коннектор.

persistBpmnGraphChange({ getResults, handlers, reconcile }) ​

Файл: ./utils/persist-bpmn-graph-change.ts.

  • Выполняет обнаруженные записи по порядку.
  • Различает попытку создания Link, полученный ответ и надёжность UUID.
  • После создания или ошибки снова загружает граф.
  • Передаёт надёжные UUID хранилищу для проверки загруженных рёбер.
  • Возвращает accepted, reconciled, persisted-unreconciled, commit-unknown или failed.

Правила управления состоянием ​

Временное и сохраняемое состояние ​

При размонтировании очищаются response, loading, error, newNodeData, lastCreatedNodeGuid, lastModel и подтверждённый контекст графа. viewbox сохраняется, чтобы не терять масштаб и положение холста.

Обнаружение изменений ​

После загрузки lastModel становится снимком. Пользователь меняет модель, компонент сравнивает её со снимком, сохраняет только разницу и обновляет основу. Если временной модели больше нельзя доверять, координатор снова вызывает настроенный метод графа и заменяет модель виджета полученным результатом.

Границы ошибок ​

  • Модули действий проверяют данные и выполняют один запрос.
  • Координатор сохранения перехватывает ошибки, снова вызывает настроенный метод графа и сохраняет известный, неизвестный или потерянный результат Link.
  • Хранилище журналирует итог и выбирает локализованное уведомление.
  • Компонент принимает только accepted; при остальных исходах сбрасывает виджет к графу из ответа метода или последней подтверждённой модели.

Практические рекомендации ​

Хранилище или composable-функция ​

Хранилище подходит для общего состояния панелей, запросов к бэкенду и истории изменений. Composable-функции подходят для реактивных преобразований, компонентной логики и вычислений без собственного жизненного цикла данных.

Очистка панели ​

При размонтировании вызывайте:

typescript
onBeforeUnmount(() => {
  useBpmnPanelsStore().cleanupPanelTransientState(panelId);
});

Получение Object графа ​

Предпочтительный статический вариант:

typescript
panel.request.objects = ['object-guid-1'];

Вариант с выбором из другой панели:

typescript
panel.request.getSelectedObjectForEventId = 'main:object-selected';

См. также ​