Overview
This page discusses methods on your durable state data types. To learn
more about how to declare your methods in a .proto
file see the
Schemas and interfaces
section.
Safety guarantees are a core feature of Reboot. Based on an RPC’s method kind, Reboot can guarantee answers to questions such as:
- Will an RPC perform any mutations of a state?
- Will an RPC perform any mutations on other states?
- If this RPC performs mutations on other states, when do those effects become visible?
- What other RPCs can run concurrently with this RPC?
Enforcing these guarantees naturally creates a safety hierarchy, in which methods can only call other methods that enforce similar or stronger guarantees. This allows Reboot to:
- Safely maximize the number of operations running in parallel on a state, while still preserving safety guarantees.
- Take over complicated safety semantics for you, like locking and transactionality across states.
- Monitor the call graph, to guarantee that transitive calls don't have unintended side-effects, even as code changes.
Kinds
The four kinds of methods in Reboot are:
Kind | |
---|---|
reader | reader methods are allowed to read from but not mutate state. Every reader is given a read-only snapshot of the state allowing multiple readers to execute concurrently on a given state instance. readers can only call other readers . |
writer | writer methods can both read from and mutate state. writer execution happens serially on a given state, allowing each update to happen atomically. To enforce write atomicity, writers can call any readers , but they cannot call other writers . |
transaction | transactions combine multiple read/write operations into a single atomic action, often across multiple states. All of the reads and writes included in the transaction will happen atomically: once a state is involved in a transaction , no other RPCs may mutate its state until that transaction is complete. If any part of the transaction fails, the entire transaction will be rolled back. transactions may call readers , writers , and other transactions . |
workflow | workflow methods asynchronously combine multiple read/write operations without holding a lock. State updates may be visible to the outside world before the workflow completes. If a failure occurs, the workflow is retried from the beginning until it runs to completion. |
Calling a method
Usually you'll call methods via generated Python, TypeScript, or React client code (or libraries that wrap the generated code).
In order to call a method on an instance of your state data types you
need to first get a reference to it, e.g., on the backend by calling
.ref()
and in React by using one of the
generated hooks.
Once you have a reference you can call methods on it. Calling a
method requires passing a context
as the first argument.
Depending on whether you're trying to call a method "inside" or
"outside" a Reboot application, it will be either a Context
or an
ExternalContext
.
Internal to a Reboot application
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.
External to a Reboot application
By "external" to a Reboot application we mean 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
context = ExternalContext(
name="send message",
url="http://localhost:9991"
)
hello = Hello.ref(id)
response = await hello.Send(context, message="Hello, World!")
const context = new ExternalContext({
name: "send message",
url: "http://localhost:9991"
});
const hello = Hello.ref(id);
const response = await hello.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.
Idempotency
You can make calls idemopotently()
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 extenal 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",
});
Reactivity
Using an ExternalContext
you can also call reader
methods reactively, for example:
- Python
- TypeScript
fig = Fig.ref(fig_id)
async for response in fig.reactively().GetPosition(context):
print(f"{fig_id}: {response}")
// COMING SOON!
From a browser or in a language without generated code
If you're trying to call into a Reboot application using React, go here!
You can call into a Reboot application directly using HTTP in
circumstances where you aren't using React or can't use an
ExternalContext
(e.g., we don't generate code in the language you're
using).
For example, to call the reader
method called Messages
on the
Hello
state type (declared in the hello.v1
package):
curl -XPOST http://localhost:9991/hello.v1.HelloMethods/Messages \
-H "x-reboot-state-ref:hello.v1.Hello:reboot-hello"
...incorporates:
localhost:9991
- your Reboot application's URLhello.v1.HelloMethods
-service
nameMessages
- method namehello.v1.Hello
- statemessage
namereboot-hello
- state ID
Also note, HTTP calls always use POST
.
Here's another example but calling a writer
method called Send
which
includes a "request" as JSON:
curl -XPOST "http://localhost:9991/hello.v1.HelloMethods/Send" \
-H "x-reboot-state-ref:hello.v1.Hello:reboot-hello" \
-d '{"message":"Hello, World!"}'