Skip to main content

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:

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!")

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:

context = ExternalContext(
name="send message",
url="http://localhost:9991",
idempotency_seed=uuid.UUID("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.

note

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:

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

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.