Идентификаторы транзакций

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

В STOMP.js транзакция создаётся через вызов метода client.begin(). В ответ возвращается объект транзакции, содержащий поле id, которое и выступает идентификатором транзакции.

Этот идентификатор:

  • генерируется клиентской библиотекой STOMP.js
  • уникален в пределах текущего соединения WebSocket
  • используется брокером для группировки кадров
  • не имеет смысла вне контекста активной сессии

Примерно внутренняя форма идентификатора представляет собой строку, часто с префиксом или случайной частью, например tx-0, tx-1 или UUID-подобное значение. Конкретный формат не стандартизирован и зависит от реализации клиента.

Роль идентификатора в кадровой модели STOMP

STOMP работает через обмен текстовыми или бинарными кадрами. В рамках транзакции каждый кадр должен быть помечен заголовком transaction, значение которого равно идентификатору активной транзакции.

Основные типы кадров, поддерживающие транзакционный контекст:

  • SEND
  • ACK
  • NACK

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

Пример логической структуры отправки сообщения:

SEND
destination:/queue/orders
transaction:tx-1
content-type:application/json

{"orderId":123}

До момента коммита сообщение не считается окончательно обработанным.

Связывание операций через transaction id

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

В рамках одной транзакции возможны:

  • несколько отправок сообщений
  • подтверждение нескольких сообщений (ACK)
  • отказ от подтверждения (NACK)
  • смешанные операции, объединённые одной логикой

Брокер не выполняет эти действия немедленно, а откладывает их до получения COMMIT.

Ключевой принцип: идентификатор транзакции — это механизм группировки, а не блокировки.

Жизненный цикл идентификатора транзакции

Идентификатор существует ровно столько, сколько живёт транзакция:

  1. Создание транзакции через BEGIN

  2. Присвоение идентификатора (tx.id)

  3. Использование идентификатора в кадровых операциях

  4. Завершение через:

    • COMMIT
    • или ABORT
  5. Уничтожение контекста транзакции на клиенте и сервере

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

Команда BEGIN и закрепление идентификатора

При вызове:

const tx = client.begin();

STOMP.js отправляет на брокер кадр:

BEGIN
transaction:tx-1

С этого момента брокер фиксирует начало транзакционного контекста и начинает ассоциировать последующие кадры с данным идентификатором.

Важно, что идентификатор закрепляется за транзакцией на стороне клиента сразу, а на стороне сервера — после обработки BEGIN.

Использование идентификатора в SEND операциях

Каждое сообщение, отправленное в рамках транзакции, должно содержать заголовок:

transaction: <tx.id>

STOMP.js автоматически добавляет этот заголовок при использовании:

tx.send({ destination: "/queue/orders" }, body);

Внутри это эквивалентно:

client.send({
  destination: "/queue/orders",
  transaction: tx.id
}, body);

Брокер в этом случае не публикует сообщение сразу в очередь или подписчикам. Оно помещается в промежуточный транзакционный буфер.

ACK и NACK в контексте идентификатора

При подписке с подтверждением (ack: client или ack: client-individual) идентификатор транзакции также используется для группировки подтверждений.

ACK в транзакции:

ACK
id:message-123
transaction:tx-2

NACK:

NACK
id:message-123
transaction:tx-2

Брокер откладывает окончательное подтверждение доставки до COMMIT. Это позволяет атомарно подтвердить или отклонить набор сообщений.

COMMIT и роль идентификатора

Команда завершения транзакции:

COMMIT
transaction:tx-3

После получения COMMIT брокер:

  • применяет все накопленные SEND
  • подтверждает ACK/NACK операции
  • очищает транзакционный буфер
  • удаляет внутренний контекст tx-3

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

ABORT и отмена по идентификатору

При отмене транзакции:

ABORT
transaction:tx-4

Брокер:

  • удаляет все отложенные SEND
  • игнорирует ACK/NACK внутри транзакции
  • возвращает сообщения в исходное состояние (если применимо)

Идентификатор используется для полного удаления связанного контекста. После ABORT все операции, помеченные данным tx.id, считаются несуществующими.

Уникальность и область действия идентификатора

Идентификатор транзакции в STOMP.js имеет строго ограниченную область действия:

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

Это означает, что два клиента могут иметь одинаковый tx-1, но брокер различает их по соединению.

Параллельные транзакции и множественные идентификаторы

STOMP.js допускает создание нескольких параллельных транзакций:

const tx1 = client.begin();
const tx2 = client.begin();

Каждая из них получает собственный идентификатор:

  • tx1.id → tx-a1
  • tx2.id → tx-b2

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

Ошибки, связанные с идентификатором

Нарушения использования идентификатора приводят к различным ошибкам протокола или поведения брокера:

  • использование несуществующего transaction id в SEND → сообщение игнорируется или отклоняется
  • COMMIT без BEGIN → ошибка протокола
  • повторный COMMIT → неопределённое поведение или ошибка
  • использование одного id после завершения транзакции → игнорирование кадров

STOMP.js не всегда явно валидирует такие ошибки на клиенте, поэтому контроль корректности идентификаторов ложится на уровень логики приложения.

Повторное использование идентификатора

Технически возможно повторное использование значения tx.id после завершения транзакции, однако это считается нежелательной практикой.

Причины:

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

Рекомендуется всегда создавать новую транзакцию вместо переиспользования идентификатора.

Идентификатор как связующий механизм консистентности

Вся транзакционная модель STOMP.js строится вокруг идеи, что идентификатор транзакции:

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

Фактически transaction id выступает временным маркером состояния, который существует только до момента фиксации или отката.

Взаимодействие с брокерами сообщений

Разные брокеры (ActiveMQ, RabbitMQ STOMP plugin, Apollo, Artemis) используют идентификатор транзакции как внутренний ключ:

  • создают временный контекст
  • буферизуют сообщения
  • связывают ACK/NACK с транзакцией
  • применяют изменения только после COMMIT

Несмотря на единый стандарт STOMP, внутренняя реализация обработки transaction id может отличаться, особенно в части оптимизации хранения и времени жизни контекста.

Синхронизация состояния через transaction id

Идентификатор позволяет гарантировать согласованность операций:

  • либо все сообщения транзакции публикуются
  • либо ни одно сообщение не попадает в систему

Это достигается исключительно за счёт того, что все кадры помечены одинаковым transaction id, а брокер откладывает их применение до финального сигнала завершения.

Таким образом, идентификатор транзакции является центральным элементом механизма атомарности в STOMP.js, связывая клиентские операции в единый управляемый поток исполнения.