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 | Coming soon! |
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
.lookup()
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
.
From "inside" a Reboot application
By "inside" 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.
From "outside" a Reboot application
By "outside" a Reboot application we mean from a (micro)service not built with Reboot (e.g., a gRPC or HTTP based service), or a lambda running in some serverless environment, or your even your laptop! Your tests are another example of "outside" a Reboot application.
In order to call a method on an instance of your state from
"outside" a Reboot application you'll need to use an
ExternalContext
, for example:
- Python
- TypeScript
context = ExternalContext(
name="send message",
url="http://localhost:9991"
)
hello = Hello.lookup(id)
response = await hello.Send(context, message="Hello, World!")
const context = new ExternalContext({
name: "send message",
url: "http://localhost:9991"
});
const hello = Hello.lookup(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.
Reactivity
Using an ExternalContext
you can also call reader
methods reactively, for example:
- Python
- TypeScript
fig = Fig.lookup(fig_id)
async for response in fig.reactively().GetPosition(context):
print(f"{fig_id}: {response}")
// COMING SOON!
HTTP
Reboot can also be accessed directly via HTTP when you don't have access to Reboot generated code (e.g., we don't generate code in the language you're using). See here for more information about Reboot's additional required headers when compared to gRPC.
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"
Or to call the writer
method called Send
:
curl -XPOST "http://localhost:9991/hello.v1.HelloMethods/Send" \
-H "x-reboot-state-ref:hello.v1.Hello:reboot-hello" \
-d '{"message":"Hello, World!"}'