Перейти к основному содержимому
Эта страница ещё не переведена

Ниже показан английский оригинал.

TransactionalPublisher

TransactionalPublisher puts several publishes in one broker transaction: every reply of a batch, or a scope a handler opens on its slot.

Messages published inside a transaction become visible together on commit, or not at all. A batch handler whose reply chain ends with .transactional() publishes every reply of the batch in one transaction, and acks the batch after the commit. A policy whose live publisher has no transactions does not compile there. A broker's prelude names its transactional policy TransactionalPublish.

By hand, a handler bounds its slot Out<impl TransactionalPublisher, Marker> and calls begin(). It returns a TransactionScope that owns the transaction, and publishes go through the scope's builder. commit() and abort() consume the scope, so a second commit or a publish after settling does not compile. A scope dropped unsettled logs a warning and leaves the broker transaction open, so a handler settles every scope it opens. The scope sends straight into the transaction: the slot's transforms and the app's publish layers do not run on its messages. This is the borrowed kind of transaction: the publisher carries one broker transaction, and one scope is open on it at a time. Kafka-like brokers, whose client holds one transaction per producer, offer this kind only. Brokers whose transactions are client buffers also offer OwnedTransactions.

Example​

Every confirmation of a batch in one transaction.

/// Every confirmation of a batch becomes visible at once, on commit.
#[subscriber("orders", reply)]
async fn confirm(orders: &[Order]) -> Vec<Confirmed> {
orders
.iter()
.map(|order| Confirmed { id: order.id })
.collect()
}

A scope opened on a slot: both entries of a payment, or neither.

#[derive(OutSlot)]
#[publishes(Debit, Credit)]
struct Ledger;

/// Both entries of a payment, or neither.
#[subscriber("payments")]
async fn book(
payment: &Payment,
Out(ledger): Out<impl TransactionalPublisher, Ledger>,
) -> HandlerOutcome {
let Ok(scope) = ledger.begin().await else {
return HandlerOutcome::retry();
};
let (id, amount) = (payment.id, payment.amount);
let debit = scope.message(&Debit { id, amount }).publish().await;
let credit = scope.message(&Credit { id, amount }).publish().await;
if debit.is_err() || credit.is_err() {
if let Err(err) = scope.abort().await {
tracing::warn!(payment.id, %err, "payments: aborting the ledger transaction failed");
}
return HandlerOutcome::retry();
}
match scope.commit().await {
Ok(()) => HandlerOutcome::ack(),
Err(_) => HandlerOutcome::retry(),
}
}
fn app(broker: MemoryBroker) -> impl App {
RustStream::new(AppInfo::new("ledger", "0.1.0")).with_broker(broker, |b| {
// One transaction per batch, committed before the batch is acked.
b.include(confirm.batch(nonzero!(64)))
.out_reply(TransactionalPublish)
.transactional();
b.include(book).out(Ledger, TransactionalPublish).build();
})
}