Управление состоянием

Управление состоянием в STOMP.js начинается с понимания того, что клиент работает поверх WebSocket и обладает собственным жизненным циклом. Этот цикл включает несколько ключевых фаз: создание клиента, подключение к брокеру, активное взаимодействие, потеря соединения и повторное подключение.

В типичном сценарии STOMP-клиент проходит следующие состояния:

  • инициализация клиента
  • попытка подключения
  • активное соединение
  • разрыв соединения
  • повторное подключение или завершение работы

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


Внутренняя модель состояния STOMP-клиента

STOMP.js не навязывает строгую state-machine, но фактически поведение клиента можно формализовать как конечный автомат. Основной объект клиента содержит:

  • транспорт WebSocket
  • конфигурацию соединения
  • список активных подписок
  • обработчики событий
  • внутренние флаги подключения

Ключевым является флаг состояния соединения, который в разных реализациях может быть представлен через connected, active, disconnected или производные состояния.

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


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

При построении стабильного приложения поверх STOMP.js необходимо дублировать состояние соединения в отдельном store. Это позволяет отделить низкоуровневую сетевую логику от UI и бизнес-логики.

Типичная модель состояния соединения включает:

  • статус соединения (connecting, connected, disconnected, error)
  • timestamp последнего подключения
  • количество попыток переподключения
  • причина разрыва соединения
  • текущий идентификатор сессии

Такое разделение позволяет избежать ситуации, когда UI напрямую зависит от событий WebSocket без промежуточного слоя управления состоянием.


Управление подписками как часть состояния

Подписки в STOMP.js являются динамическими объектами, которые необходимо рассматривать как часть состояния приложения. Каждая подписка обладает:

  • destination (топик или очередь)
  • callback обработчик
  • идентификатор подписки
  • флаг активности

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

Практически это означает наличие слоя, который хранит декларативное описание подписок, а не только их runtime-объекты.


Реактивное состояние сообщений

Сообщения, получаемые через STOMP, также становятся частью управляемого состояния. В зависимости от архитектуры приложения они могут:

  • немедленно обрабатываться и исчезать
  • сохраняться в локальном store
  • агрегироваться по каналам
  • фильтроваться до попадания в бизнес-логику

Распространённая ошибка — обработка сообщений напрямую в callback подписки без промежуточного слоя. Это приводит к сильной связности и усложняет тестирование.

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


Состояние переподключения

Одним из наиболее критичных аспектов является управление переподключением. STOMP.js сам по себе не решает задачу устойчивого reconnect-поведения, поэтому состояние переподключения выносится в прикладной слой.

Обычно выделяются следующие параметры состояния:

  • текущее количество попыток reconnect
  • максимальное количество попыток
  • стратегия задержки (фиксированная, экспоненциальная)
  • текущее состояние backoff-таймера

Экспоненциальный backoff часто используется для предотвращения перегрузки сервера при массовых реконнектах.


Хранение состояния в архитектуре приложения

При интеграции STOMP.js в современные frontend-архитектуры состояние обычно размещается в одном из следующих слоёв:

Redux-подобные хранилища

В Redux состояние STOMP обычно моделируется как часть глобального state tree:

  • connection.status
  • connection.error
  • subscriptions.byId
  • messages.byChannel

Такой подход обеспечивает предсказуемость и трассируемость изменений состояния.


Vuex / Pinia

В реактивных системах состояние STOMP становится частью реактивного store. Основное преимущество — автоматическое обновление UI при изменении статуса соединения или поступлении сообщений.

Однако требуется аккуратно отделять side-effects (подключение, reconnect) от мутаций состояния.


Локальные сервисы состояния

В более лёгких архитектурах состояние инкапсулируется в отдельном сервисе, который предоставляет:

  • методы подключения
  • методы подписки
  • поток состояния через observable или event emitter

Такой подход уменьшает зависимость от конкретного state manager, но требует строгой дисциплины в управлении побочными эффектами.


Консистентность состояния при разрывах соединения

Разрыв WebSocket соединения приводит к рассинхронизации нескольких уровней состояния:

  • подписки становятся невалидными
  • сообщения могут теряться
  • UI продолжает отображать устаревшие данные

Для обеспечения консистентности используется стратегия восстановления состояния:

  1. фиксация текущего состояния подписок
  2. очистка runtime подписок
  3. повторное подключение
  4. восстановление подписок
  5. синхронизация состояния через серверные события

Важный аспект — идемпотентность восстановления, поскольку reconnect может происходить многократно.


Буферизация сообщений и временное состояние

При нестабильном соединении возникает необходимость временного хранения сообщений. Это состояние делится на два типа:

  • исходящие сообщения, ожидающие отправки
  • входящие сообщения, ожидающие обработки

Буферизация отправки особенно важна, если приложение допускает офлайн-режим или нестабильную сеть.

Структура состояния буфера обычно включает:

  • очередь сообщений
  • timestamp постановки в очередь
  • приоритет сообщения
  • статус доставки

Управление конкурентными изменениями состояния

STOMP-клиент может одновременно:

  • переподключаться
  • получать сообщения
  • отправлять сообщения
  • восстанавливать подписки

Это создаёт конкурентные изменения состояния, которые необходимо сериализовать.

Типичные проблемы:

  • подписка создаётся до завершения подключения
  • сообщения отправляются в момент реконнекта
  • состояние UI обновляется до фактического подтверждения соединения

Решение заключается в введении промежуточного слоя оркестрации, который гарантирует последовательность операций.


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

Наиболее устойчивый подход к управлению состоянием STOMP заключается в переходе к событийной модели.

Все изменения представляются как события:

  • CONNECTING
  • CONNECTED
  • DISCONNECTED
  • MESSAGE_RECEIVED
  • SUBSCRIPTION_ADDED
  • SUBSCRIPTION_REMOVED
  • RECONNECT_ATTEMPT

Каждое событие обновляет централизованное состояние через редьюсер или обработчик состояния.

Такой подход позволяет:

  • воспроизводить состояние
  • логировать поток событий
  • тестировать поведение клиента
  • внедрять time-travel debugging

Изоляция состояния STOMP от бизнес-логики

Ключевой принцип архитектуры — разделение транспортного состояния и доменного состояния.

STOMP отвечает только за:

  • соединение
  • передачу сообщений
  • подписки

Бизнес-логика должна оперировать:

  • агрегированными событиями
  • доменными сущностями
  • результатами обработки сообщений

Это предотвращает утечку сетевых деталей в бизнес-слой и упрощает масштабирование системы.


Состояние как источник истины

В зрелых архитектурах STOMP-соединение рассматривается как вторичный источник данных. Первичным становится централизованное состояние приложения.

STOMP в этом случае выполняет роль транспортного канала, а не управляющего компонента.

Такой подход позволяет:

  • заменять брокер без изменения бизнес-логики
  • тестировать систему без реального WebSocket
  • восстанавливать состояние из логов событий
  • масштабировать клиентскую архитектуру

Управление жизненным состоянием в долгоживущих соединениях

Долгоживущие WebSocket-соединения требуют постоянного контроля состояния:

  • деградация соединения без полного разрыва
  • частичная потеря сообщений
  • задержки доставки
  • рассинхронизация времени сервера и клиента

Для этого вводятся дополнительные метрики состояния:

  • latency
  • heartbeat interval
  • last received message timestamp
  • connection health score

Эти параметры позволяют принимать решения о принудительном переподключении даже при формально активном соединении.