From within your app
By "internal" to a Reboot application, we specifically mean within one
of the methods that you've implemented for your
servicers. Each of those
methods takes a context argument that you can use to make calls:
ReaderContext- Passed to areader.WriterContext- Passed to awriter.TransactionContext- Passed to atransaction.WorkflowContext- Passed to aworkflow.
The types of these contexts allow Reboot (as well as static type
checkers like mypy, Pyright, or tsc) to enforce its safety
guarantees throughout the call
graph.
Making calls to other durable data types
Within your servicer methods, you can call methods on other durable data types
by first getting a reference to it using .ref(), then calling
the method while passing along the context from your servicer method:
- Python
- TypeScript
async def transfer(
self,
context: TransactionContext,
request: Bank.TransferRequest,
) -> None:
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)
async transfer(
context: TransactionContext,
request: Bank.TransferRequest
): Promise<Bank.PartialTransferResponse> {
const fromAccount = Account.ref(request.fromAccountId);
const toAccount = Account.ref(request.toAccountId);
await fromAccount.withdraw(context, { amount: request.amount });
await toAccount.deposit(context, { amount: request.amount });
Accessing local state
Within your servicer methods, you can access the state of the current
instance via self.state (Python) or this.state (TypeScript class-based).
In TypeScript, if you're using the object literal syntax, state is
passed as a parameter to your method.
- Python
- TypeScript Class
- TypeScript Object Literal
async def send(
self,
context: WriterContext,
request: SendRequest,
) -> SendResponse:
message = request.message
self.state.messages.extend([message])
return SendResponse()
async send(
context: WriterContext,
request: ChatRoom.SendRequest
): Promise<ChatRoom.PartialSendResponse> {
this.state.messages.push(request.message);
return {};
}
send: async (
context: WriterContext,
state: ChatRoom.State,
request: ChatRoom.SendRequest
): Promise<[ChatRoom.State, ChatRoom.PartialSendResponse]> => {
state.messages.push(request.message);
return [state, {}];
}
Only Writer, Transaction, and Workflow methods can modify
state. Reader methods can only read state.
Accessing your state ID
You can access the ID of the current state instance via
context.state_id (Python) or context.stateId (TypeScript):
- Python
- TypeScript
async def get_id(
self,
context: ReaderContext,
request: GetIdRequest,
) -> GetIdResponse:
current_id = context.state_id
return GetIdResponse(id=current_id)
async getId(
context: ReaderContext,
request: GetIdRequest
): Promise<GetIdResponse> {
const currentId = context.stateId;
return { id: currentId };
}
Making concurrent calls
You can make concurrent calls using
asyncio.gather() (Python) or Promise.all() (TypeScript):
- Python
- TypeScript
all_customer_balances: list[CustomerAccounts] = await asyncio.gather(
*[
customer_accounts(entry.value.decode())
for entry in customer_ids.entries
]
)
return {
balances: await Promise.all(
accountIds.map(async (accountId) => {
const { amount } = await Account.ref(accountId).balance(context);
return { accountId, balance: amount };
})
),
};
Using concurrent calls can significantly improve performance when gathering data from multiple state instances.
Context constraints
Each context type constrains what methods you can call, enforcing
Reboot's safety guarantees:
| Context Type | Can Call | Can Modify State |
|---|---|---|
ReaderContext | Only reader methods | No |
WriterContext | Only reader methods | Yes (own state only) |
TransactionContext | reader and writer methods | Yes |
WorkflowContext | Any method type | Yes |
Type checkers like mypy, Pyright, and tsc will catch violations
of these constraints at compile time, helping you catch bugs before
runtime.
Calling writers from other writers
Notice that WriterContext cannot call other writer methods
directly. This is by design to prevent
unsafe, partial updates in the case that one of the calls fails.
If you need to call multiple writer methods atomically, use a
transaction method instead.
Scheduling writers from writers
The one exception to the "writers can't call writers" rule is through scheduled tasks. A writer can schedule another writer to execute later:
- Python
- TypeScript
async def open(
self,
context: WriterContext,
) -> None:
self.state.balance = 0.0
await self.ref().schedule(
when=timedelta(seconds=1),
).interest(context)
async open(
context: WriterContext,
request: Account.OpenRequest
): Promise<Account.PartialOpenResponse> {
// Since this is a constructor, we are setting the initial state of the
// state machine.
this.state.customerName = request.customerName;
// We'd like to send the new customer a welcome email, but that can be
// done asynchronously, so we schedule it as a task.
const taskId = await this.ref().schedule().welcomeEmail(context);
return { welcomeEmailTaskId: taskId };
}
This works because .schedule() creates a separate task that will
execute independently, not as part of the current writer's execution.
See Tasks for more details.