Skip to main content

Transactions

A transaction method gets exclusive, atomic access to state in order to update it (similar to a writer). But unlike a writer, a transaction can also call other writer and transaction methods. Those reads and writes all occur as a (distributed) ACID transaction.

A transaction method gets passed a context of type TransactionContext. A TransactionContext can be used to make calls to reader's, writer's, and other transaction methods (although currently nested calls to a transaction method may only read/write mutually exclusive state.)

Here's an example of a transaction method called Transfer on our Bank state that deposits money in one account and withdraws it from another:

from bank.v1.bank_rbt import Bank

class BankServicer(Bank.Servicer):

async def Transfer(
self,
context: TransactionContext,
request: TransferRequest,
) -> TransferResponse:
from_account = Account.ref(request.from_account_id)
to_account = Account.ref(request.to_account_id)

await from_account.Withdraw(context, amount=request.amount)
await to_account.Deposit(context, amount=request.amount)

return TransferResponse()

Again, like writers, state can be modified directly in a transaction method, for example here is an example from the SignUp method for Bank that stores the account IDs:

# Transactions like writers can alter state directly.
self.state.account_ids.append(account.state_id)

In addition to returning a response and updating state, a transaction can also schedule async tasks, which are atomically started (or enqueued) if and only if the transaction completes successfully.