В протоколе STOMP транзакция представляет собой именованный контекст,
внутри которого сообщения группируются и отправляются атомарно. Клиент
создаёт транзакцию через идентификатор, после чего все
SEND-операции помечаются этим идентификатором и не
публикуются в брокер до момента COMMIT. При
ABORT все накопленные сообщения отбрасываются.
= {, _1, _2, , }
STOMP.js реализует эту модель как тонкую обёртку над фреймами
протокола, где транзакция существует исключительно на стороне брокера и
идентифицируется строковым transaction id.
STOMP не поддерживает вложенные транзакции в классическом смысле (как savepoints в SQL или nested transactions в некоторых брокерах JMS). Каждая транзакция является независимой сущностью, и брокер не хранит контекст родительских/дочерних связей.
Попытка создать вложенность на уровне клиентской логики не приводит к реальной иерархии в брокере. Если несколько транзакций активны одновременно, они рассматриваются как отдельные контексты.
Ключевое ограничение:
savepointrollback to nested txВложенные транзакции в STOMP.js — это исключительно архитектурный паттерн, реализуемый на стороне клиента. Он строится как композиция нескольких транзакций с явной связью между ними:
Такая модель ближе к концепции saga, чем к классическим ACID вложенным транзакциям.
В STOMP.js транзакции идентифицируются строками, и именно это позволяет строить логическую вложенность:
tx-parent-1001tx-parent-1001.step-1tx-parent-1001.step-2Брокер не интерпретирует эту структуру, но клиентская логика может восстанавливать связь между транзакциями.
В STOMP.js транзакции создаются через объект соединения:
const tx = client.begin();
client.publish({
destination: "/queue/orders",
body: JSON.stringify({ id: 1 }),
transaction: tx.id
});
tx.commit();
При необходимости отмены:
tx.abort();
Каждый publish внутри транзакции должен явно содержать
ссылку на transaction.id.
Вложенность достигается через каскадный контроль нескольких транзакций:
const parentTx = client.begin();
try {
const step1Tx = client.begin();
client.publish({
destination: "/queue/step1",
body: "A",
transaction: step1Tx.id
});
step1Tx.commit();
const step2Tx = client.begin();
client.publish({
destination: "/queue/step2",
body: "B",
transaction: step2Tx.id
});
step2Tx.commit();
client.publish({
destination: "/queue/finalize",
body: "DONE",
transaction: parentTx.id
});
parentTx.commit();
} catch (e) {
parentTx.abort();
}
Здесь вложенность является логической, но не технической. Каждый шаг может быть независимо закоммичен или откатан до того, как завершится родительская транзакция.
Чтобы имитировать вложенность корректно, требуется внешнее состояние:
Пример структуры состояния:
const txState = {
parentId: "tx-100",
steps: [
{ id: "tx-100-1", status: "committed" },
{ id: "tx-100-2", status: "pending" }
]
};
Такой подход позволяет реализовать частичное восстановление при сбоях.
Так как брокер не поддерживает каскадный rollback, каждая дочерняя транзакция должна иметь компенсирующую операцию.
try {
const tx1 = client.begin();
client.publish({
destination: "/queue/debit",
body: JSON.stringify({ amount: 100 }),
transaction: tx1.id
});
tx1.commit();
} catch (e) {
client.publish({
destination: "/queue/compensate-debit",
body: JSON.stringify({ reason: e.message })
});
}
Компенсация становится основным механизмом согласованности.
Реализация STOMP-транзакций зависит от брокера:
Общее правило: вложенность никогда не поддерживается нативно.
Каждая транзакция в STOMP:
Это означает, что «вложенность» не влияет на конкурентное поведение брокера.
При проектировании системы с псевдо-вложенными транзакциями необходимо учитывать:
В системах с высокой распределённостью вложенные транзакции заменяются оркестрацией:
STOMP.js в таком случае используется как транспортный слой, а не как транзакционный менеджер.
Логически вложенные транзакции можно представить как граф зависимостей:
G = (V, E), ; V = {tx_1, tx_2, }, ; E = {(tx_i tx_j)}
где вершины — транзакции, а рёбра — зависимости выполнения. Такая модель отражает реальное поведение клиентской логики лучше, чем попытки использовать «вложенность» напрямую.
Так как брокер не обеспечивает вложенную атомарность, согласованность достигается через:
Каждое сообщение становится частью более широкой бизнес-транзакции, а не технической единицей брокера.
Если одна из дочерних транзакций завершается с ошибкой:
Это приводит к модели eventual consistency, где вложенность существует только на уровне бизнес-логики.