Skip to main content

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
readerreader 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.
writerwriter 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.
transactiontransactions 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.
workflowworkflow 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 a reader.
  • WriterContext - Passed to a writer.
  • TransactionContext - Passed to a transaction.
  • WorkflowContext - Passed to a workflow.

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:

context = ExternalContext(
name="send message",
url="http://localhost:9991"
)

hello = Hello.ref(id)

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:

context = ExternalContext(
name="send message",
url="http://localhost:9991",
idempotency_seed=uuid.UUID("123e4567-e89b-12d3-a456-426614174000"),
)

Reactivity

Using an ExternalContext you can also call reader methods reactively, for example:

fig = Fig.ref(fig_id)
async for response in fig.reactively().GetPosition(context):
print(f"{fig_id}: {response}")

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 URL
  • hello.v1.HelloMethods - service name
  • Messages - method name
  • hello.v1.Hello - state message name
  • reboot-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!"}'