Транзакции в STOMP.js строятся поверх семантики протокола STOMP и представляют собой механизм группировки нескольких кадров SEND/ACK/NACK в единый атомарный блок, который либо фиксируется целиком, либо откатывается без частичного применения. Подтверждение транзакции в этом контексте включает сразу несколько уровней: подтверждение со стороны клиента, подтверждение со стороны брокера и подтверждение доставки сообщений в рамках транзакционного контекста.
Транзакция в STOMP задаётся идентификатором и ограничивает область действия команд. Любое сообщение, отправленное с указанием transaction, не считается окончательно обработанным до получения команды COMMIT.
Базовая последовательность кадров:
В STOMP.js транзакции инкапсулируются объектом transaction, который создаётся через клиентское API и привязывается к идентификатору транзакции.
Транзакция создаётся на уровне клиента и получает уникальный идентификатор, который используется во всех последующих кадрах.
const tx = client.begin();
tx.send({
destination: '/queue/orders',
body: JSON.stringify({ id: 1, status: 'created' })
});
tx.commit();
В момент вызова begin клиент отправляет брокеру кадр BEGIN с заголовком transaction. Все последующие операции маркируются этим идентификатором.
Ключевой момент: сообщение, отправленное внутри транзакции, не покидает логическую область фиксации до COMMIT.
Подтверждение транзакции происходит только после получения COMMIT. До этого момента брокер:
После COMMIT брокер выполняет атомарную фиксацию:
Если вместо COMMIT приходит ABORT, брокер полностью откатывает изменения, как будто транзакции не существовало.
Подтверждение транзакции нельзя путать с подтверждением доставки сообщения (message acknowledgment). В STOMP существует два уровня:
Транзакция влияет на семантический уровень. Сообщение считается гарантированно обработанным только после COMMIT.
Особенности поведения:
Отдельный механизм подтверждения в STOMP — заголовок receipt. Он позволяет получить явное подтверждение получения кадра брокером.
const tx = client.begin({
receipt: 'tx-1'
});
tx.commit();
При использовании receipt брокер отправляет кадр RECEIPT после успешной обработки COMMIT. Это подтверждение отличается от логического подтверждения транзакции:
Таким образом формируется двухуровневая модель надёжности.
В STOMP.js транзакционный объект обычно реализует следующие функции:
При вызове send библиотека автоматически добавляет header:
transaction: <transaction-id>
Пример более полного сценария:
const tx = client.begin();
tx.send({
destination: '/queue/payments',
body: JSON.stringify({ amount: 500 })
});
tx.send({
destination: '/queue/audit',
body: JSON.stringify({ event: 'payment_created' })
});
tx.commit();
Обе операции отправки становятся атомарными с точки зрения брокера.
ABORT полностью отменяет все операции, выполненные внутри транзакции. Это особенно важно при ошибках бизнес-логики на клиенте.
const tx = client.begin();
try {
tx.send({
destination: '/queue/orders',
body: JSON.stringify({ id: 10 })
});
throw new Error('validation failed');
tx.commit();
} catch (e) {
tx.abort();
}
После ABORT брокер не сохраняет никаких следов транзакционных операций.
Транзакции напрямую влияют на порядок доставки сообщений. В рамках одного transaction-id соблюдается последовательность отправки:
Это критично для сценариев:
ACK также может быть частью транзакции. Это позволяет атомарно подтверждать обработку входящих сообщений.
const tx = client.begin();
subscription.ack(message, { transaction: tx });
tx.commit();
Здесь ACK не считается финальным до COMMIT. Если транзакция будет отменена, сообщение снова станет доступным для доставки.
Если транзакция не была подтверждена из-за разрыва соединения или ABORT, брокер возвращает сообщения в очередь. Это приводит к повторной доставке, поэтому обработчики должны быть идемпотентными.
Ключевые сценарии:
Транзакции STOMP не являются распределёнными в классическом смысле. Они:
Подтверждение транзакции всегда локально для конкретной сессии STOMP.
Разные брокеры реализуют транзакции с нюансами:
Однако для STOMP.js это прозрачно, так как библиотека работает на уровне кадров протокола.
Таймауты транзакции обычно не задаются на уровне STOMP, но зависят от:
При длительном «висящем» транзакционном состоянии брокер может разорвать соединение, что автоматически приводит к ABORT.
Клиентская сторона хранит минимальное состояние:
После commit или abort объект транзакции считается завершённым и больше не используется.
Особенность STOMP.js: повторный вызов операций на завершённой транзакции приводит к ошибке или игнорированию в зависимости от реализации клиента.
Подтверждение транзакции усиливает гарантию доставки до уровня:
Но не обеспечивает exactly-once без внешней идемпотентности.
Основная ценность механизма заключается в том, что набор операций SEND/ACK/NACK становится неделимой единицей обработки в брокере.