From outside your app
"External" to a Reboot application means from a (micro)service (e.g., a gRPC or HTTP based service) or a lambda (e.g., using some serverless framework), or even from your laptop! Your tests are another example of "external".
In order to call a method on an instance of your state externally,
you'll need to use an ExternalContext, for example:
- Python
- TypeScript
from reboot.aio.external import ExternalContext
context = ExternalContext(
name="send message",
url="http://localhost:9991"
)
chat_room = ChatRoom.ref(id)
response = await chat_room.send(context, message="Hello, World!")
import { ExternalContext } from "@reboot-dev/reboot";
const context = new ExternalContext({
name: "send message",
url: "http://localhost:9991"
});
const chat_room = ChatRoom.ref(id);
const response = await chat_room.send(context, { message: "Hello, World!" });
The url argument specifies where your Reboot application is
running, e.g., on your laptop via rbt dev, in a container on
Kubernetes via rbt serve (where url will likely route to your
Kubernetes gateway), or in the cloud via rbt cloud.
Because an ExternalContext is used outside of Reboot, a series of
Reboot methods called using an ExternalContext don't have any
atomicity guarantees with regard to one another (although the
individual methods executing within Reboot will of course still have
their own atomicity). If you are calling multiple Reboot methods that
you would like to happen atomically, you can move those calls into a
transaction, and then
call that method from your client.
Making calls idempotently
You can make calls idempotently()
using an ExternalContext so
that you only perform operations once. You can even pass an
idempotency seed, e.g., a UUID, to an ExternalContext when
constructing it so that every time you re-run code using that
ExternalContext it will generate the same idempotency keys so that
your external code will execute "where it left off" from the last time
it was running. This is similar to trying to write code that
idempotently
converges,
just external to your Reboot application. Here's an example:
- Python
- TypeScript
context = ExternalContext(
name="send message",
url="http://localhost:9991",
idempotency_seed=uuid.UUID("123e4567-e89b-12d3-a456-426614174000"),
)
const context = new ExternalContext({
name: "send message",
url: "http://localhost:9991",
idempotencySeed="123e4567-e89b-12d3-a456-426614174000",
});
Calling readers reactively
You can also call reader methods reactively: you receive a new
result every time the state changes. Each result is either the reader's
response or the error it raised (aborted): a declared error, a
denied authorizer(), a state that does not exist yet. An error does
not end the read: the subscription stays open and yields again once the
state changes, so you decide whether to keep reading, stop, or raise the
error yourself. Failed connections are retried for you.
The Node.js ExternalContext cannot read reactively yet. From Node.js,
you can use a WebContext from @reboot-dev/reboot-web instead.
Running it in Node.js is not supported beyond the case shown below. It
needs the fetch and WebSocket globals (Node.js 22 has both by
default; Node.js 20 needs --experimental-websocket).
For example:
- Python
- TypeScript
async for response, aborted in chat_room.reactively().messages(context):
if aborted is not None:
# The reader raised an error, e.g., the chat room does not
# exist yet. The read continues and yields again once the
# state changes.
print(f"Could not read messages: {aborted}")
continue
assert response is not None
print(response.messages)
if "Hello, World!" in response.messages:
break
const [responses] = await chatRoom.reactively().messages(context);
for await (const { response, aborted } of responses) {
if (aborted !== undefined) {
// The reader raised an error, e.g., the chat room does not
// exist yet. The read continues and yields again once the
// state changes.
console.log(`Could not read messages: ${aborted.message}`);
continue;
}
console.log(response.messages);
if (response.messages.includes("Hello, World!")) {
break;
}
}
Making concurrent calls
When calling multiple Reboot methods externally, you can use the
same concurrency patterns described in
Making concurrent calls.
For Python, the concurrently helper provides adaptive
concurrency limits and streaming results — see
concurrently (Python)
for details and examples.