Транзакции в STOMP поверх WebSocket предоставляют механизм атомарной
отправки сообщений: группа сообщений либо фиксируется целиком, либо
откатывается без попадания в брокер. В контексте STOMP.js транзакции
реализуются на уровне STOMP-протокола и поддерживаются брокером
(например, ActiveMQ, RabbitMQ, Apollo, Artemis), а клиентская библиотека
лишь управляет жизненным циклом транзакции и маркировкой кадров
SEND.
Транзакция в STOMP представляет собой логическую обёртку над набором
операций SEND. Сообщения, отправленные в рамках транзакции,
не становятся доступными подписчикам до момента COMMIT. При
ABORT все сообщения транзакции отбрасываются.
В STOMP.js транзакция создаётся через объект соединения клиента.
Базовая точка входа — метод begin, возвращающий объект
транзакции.
const transaction = client.begin();
После вызова создаётся идентификатор транзакции
(transaction.id), который автоматически используется при
отправке сообщений в рамках этого контекста.
Транзакционный объект обычно содержит:
id — уникальный идентификатор транзакцииcommit() — фиксация всех операцийabort() — откат всех операцийsend() — отправка сообщений внутри транзакцииКаждое сообщение, отправленное в рамках транзакции, должно содержать
заголовок transaction. STOMP.js автоматически добавляет
этот заголовок при использовании метода
transaction.send.
transaction.send({
destination: "/queue/orders",
body: JSON.stringify({
orderId: 123,
status: "created"
})
});
Фактически клиент формирует STOMP FRAME следующего вида:
SEND
destination:/queue/orders
transaction:tx-1
content-type:application/json
{"orderId":123,"status":"created"}
До выполнения COMMIT брокер удерживает сообщения в
изолированном состоянии.
Транзакция в STOMP проходит строго определённые этапы:
BEGINSENDCOMMIT или ABORTconst tx = client.begin();
На этом этапе брокер фиксирует создание транзакции и резервирует её идентификатор.
tx.send({
destination: "/queue/payments",
body: JSON.stringify({
userId: 42,
amount: 1000
}),
headers: {
persistent: "true"
}
});
Каждое сообщение маркируется транзакционным идентификатором. Важно, что сообщения не доставляются подписчикам до завершения транзакции.
Внутри одной транзакции допускается отправка множества сообщений в разные очереди:
tx.send({ destination: "/queue/a", body: "A1" });
tx.send({ destination: "/queue/b", body: "B1" });
tx.send({ destination: "/queue/a", body: "A2" });
tx.commit();
После COMMIT брокер атомарно публикует все накопленные
сообщения. Они становятся видимыми подписчикам одновременно.
tx.abort();
При ABORT брокер удаляет все сообщения транзакции, как
если бы они никогда не отправлялись.
Транзакции в STOMP обеспечивают атомарность на уровне брокера, но не на уровне сети WebSocket. Это означает:
commit дошёл до брокера — изменения
фиксируютсяcommit — транзакция может
быть отменена брокеромТаким образом, STOMP.js не реализует транзакции самостоятельно, а
лишь передаёт управляющие кадры BEGIN, COMMIT,
ABORT.
STOMP использует специальные команды:
BEGIN
transaction:tx-123
SEND
destination:/queue/example
transaction:tx-123
content-type:text/plain
hello
COMMIT
transaction:tx-123
ABORT
transaction:tx-123
STOMP.js абстрагирует формирование этих кадров, но логика остаётся идентичной.
STOMP.js реализует транзакции как объект-обёртку над клиентским соединением. Важные особенности:
commit или abort игнорируется
или вызывает ошибку в зависимости от реализации брокераsend внутри транзакции используют один
transaction-idПример полной цепочки:
const tx = client.begin();
try {
tx.send({
destination: "/queue/orders",
body: JSON.stringify({ id: 1 })
});
tx.send({
destination: "/queue/audit",
body: JSON.stringify({ event: "order_created" })
});
tx.commit();
} catch (e) {
tx.abort();
}
Ошибки в транзакциях STOMP делятся на несколько типов:
commitCOMMITВ случае ошибок брокера обычно отправляется STOMP frame
ERROR, после чего соединение может быть закрыто.
STOMP позволяет параллельно открывать несколько транзакций:
const tx1 = client.begin();
const tx2 = client.begin();
tx1.send({ destination: "/queue/a", body: "A" });
tx2.send({ destination: "/queue/b", body: "B" });
tx1.commit();
tx2.abort();
Каждая транзакция имеет собственный идентификатор и полностью изолирована от других.
Использование транзакций влияет на поведение брокера:
COMMITВ системах с высокой нагрузкой транзакции используются точечно, когда требуется гарантированная атомарность нескольких сообщений.
Разные брокеры по-разному реализуют транзакционность:
Некоторые брокеры могут эмулировать транзакции или ограничивать их
использование только для SEND.
Заголовки сообщений внутри транзакции не отличаются от обычных
SEND, за исключением обязательного
transaction:
tx.send({
destination: "/queue/logs",
headers: {
priority: "9",
persistent: "true",
"content-type": "application/json"
},
body: JSON.stringify({ message: "critical event" })
});
Все заголовки фиксируются брокером только после
COMMIT.
Транзакции создают модель отложенной публикации:
Это особенно важно для сценариев:
Несмотря на удобство, модель имеет ограничения:
COMMITSTOMP.js остаётся транспортным уровнем, не реализующим полноценную транзакционную систему, а лишь управляющим STOMP-командами, которые интерпретируются сервером.