Транзакция в протоколе STOMP позволяет объединить несколько операций
отправки и подтверждения сообщений в единый атомарный блок. Пока
транзакция не завершена командой COMMIT, брокер
рассматривает все действия как незавершённые. При отмене через
ABORT изменения откатываются.
В STOMP.js транзакции используются для:
ACK;Транзакционный режим особенно важен при работе с финансовыми операциями, заказами, синхронизацией данных, системами логирования и обработкой событий.
begin()Начало транзакции в STOMP.js выполняется методом begin()
объекта клиента.
Базовый синтаксис:
const transaction = client.begin();
После вызова метода создаётся объект транзакции, содержащий уникальный идентификатор и методы управления.
Пример:
import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://localhost:15674/ws'
});
client.onConn ect = () => {
const transaction = client.begin();
console.log(transaction.id);
};
client.activate();
После вызова begin() клиент отправляет брокеру
STOMP-фрейм:
BEGIN
transaction:tx-1
^@
Где:
BEGIN — команда протокола;transaction — идентификатор транзакции;tx-1 — уникальный ID.Все последующие операции могут быть связаны с этим идентификатором.
Метод begin() возвращает специальный объект
транзакции.
Типичная структура:
const tx = client.begin();
Объект содержит:
idcommit()abort()Пример:
const tx = client.begin();
console.log(tx.id);
tx.commit();
Если идентификатор не указан вручную, STOMP.js генерирует его автоматически.
Пример автоматически созданного ID:
tx-0
tx-1
tx-2
Это предотвращает конфликты между несколькими активными транзакциями.
STOMP.js позволяет задавать ID транзакции вручную.
Пример:
const tx = client.begin('order-processing');
Тогда будет отправлен фрейм:
BEGIN
transaction:order-processing
^@
Пользовательские идентификаторы полезны:
STOMP допускает параллельное существование нескольких транзакций.
Пример:
const tx1 = client.begin('payments');
const tx2 = client.begin('notifications');
После этого операции можно разделять:
client.publish({
destination: '/queue/payments',
body: 'Payment completed',
headers: {
transaction: tx1.id
}
});
client.publish({
destination: '/queue/notifications',
body: 'User notified',
headers: {
transaction: tx2.id
}
});
Каждая транзакция обрабатывается независимо.
Сообщение становится частью транзакции только при указании заголовка
transaction.
Пример:
const tx = client.begin();
client.publish({
destination: '/queue/tasks',
body: 'Task 1',
headers: {
transaction: tx.id
}
});
Без заголовка сообщение будет отправлено немедленно вне транзакции.
Одна транзакция может включать множество сообщений.
Пример:
const tx = client.begin();
client.publish({
destination: '/queue/logs',
body: 'Log entry 1',
headers: {
transaction: tx.id
}
});
client.publish({
destination: '/queue/logs',
body: 'Log entry 2',
headers: {
transaction: tx.id
}
});
client.publish({
destination: '/queue/logs',
body: 'Log entry 3',
headers: {
transaction: tx.id
}
});
tx.commit();
Брокер применит изменения только после COMMIT.
Транзакции могут использоваться не только для отправки, но и для подтверждения сообщений.
Пример:
client.subscribe('/queue/orders', message => {
const tx = client.begin();
try {
processOrder(message);
message.ack({
transaction: tx.id
});
tx.commit();
} catch (error) {
tx.abort();
}
}, {
ack: 'client'
});
В этом случае подтверждение сообщения становится частью транзакции.
Наиболее часто транзакции используются вместе с:
clientclient-individualПример:
client.subscribe('/queue/events', handler, {
ack: 'client-individual'
});
Режим auto практически не сочетается с транзакционной
обработкой, поскольку сообщения подтверждаются автоматически.
Транзакции оправданы в следующих сценариях:
const tx = client.begin();
sendDebit(tx);
sendCredit(tx);
tx.commit();
const tx = client.begin();
writeMainLog(tx);
writeAuditLog(tx);
tx.commit();
const tx = client.begin();
events.forEach(event => {
client.publish({
destination: '/topic/events',
body: JSON.stringify(event),
headers: {
transaction: tx.id
}
});
});
tx.commit();
Ошибка:
const tx = client.begin();
при отсутствии соединения.
Правильный вариант:
client.onConn ect = () => {
const tx = client.begin();
};
Ошибка:
client.begin();
client.publish({
destination: '/queue/test',
body: 'data'
});
Транзакция создаётся, но её ID нигде не используется.
Правильно:
const tx = client.begin();
client.publish({
destination: '/queue/test',
body: 'data',
headers: {
transaction: tx.id
}
});
Ошибка:
client.publish({
destination: '/queue/test',
body: 'data',
headers: {
transaction: 'unknown-tx'
}
});
Брокер может отклонить такую операцию.
STOMP.js не хранит глобальный список транзакций. Управление их состоянием полностью лежит на приложении.
Обычно применяется собственная система хранения:
const activeTransactions = new Map();
const tx = client.begin();
activeTransactions.set(tx.id, tx);
При отладке полезно фиксировать:
Пример:
const tx = client.begin();
console.log('Transaction started:', tx.id);
Сам STOMP-протокол не содержит встроенного механизма таймаута транзакций. Если транзакция не завершена:
Поэтому часто реализуют пользовательский таймер:
const tx = client.begin();
const timeout = setTimeout(() => {
tx.abort();
}, 5000);
Транзакции удобно интегрируются в асинхронный код.
Пример:
async function saveBatch(items) {
const tx = client.begin();
try {
for (const item of items) {
client.publish({
destination: '/queue/items',
body: JSON.stringify(item),
headers: {
transaction: tx.id
}
});
}
tx.commit();
} catch (error) {
tx.abort();
throw error;
}
}
Разные брокеры могут по-разному реализовывать транзакции:
COMMIT.Наиболее популярные брокеры с поддержкой STOMP:
При вызове:
client.begin();
STOMP.js:
BEGIN-фрейм;Метод commit() позже отправляет:
COMMIT
transaction:tx-1
^@
А abort():
ABORT
transaction:tx-1
^@
import { Client } from '@stomp/stompjs';
const client = new Client({
brokerURL: 'ws://localhost:15674/ws'
});
client.onConn ect = () => {
const tx = client.begin('batch-import');
try {
client.publish({
destination: '/queue/import',
body: 'Record 1',
headers: {
transaction: tx.id
}
});
client.publish({
destination: '/queue/import',
body: 'Record 2',
headers: {
transaction: tx.id
}
});
client.publish({
destination: '/queue/import',
body: 'Record 3',
headers: {
transaction: tx.id
}
});
tx.commit();
console.log('Transaction committed');
} catch (error) {
tx.abort();
console.error('Transaction aborted', error);
}
};
client.activate();