Journaling Results
Restate uses an execution log for replay after failures and suspensions. This means that non-deterministic results (e.g. database responses, UUID generation) need to be stored in the execution log. The SDK offers some functionalities to help you with this:
- Journaled actions: Run any block of code and store the result in Restate. Restate replays the result instead of re-executing the block on retries.
Awaitable
combinators: Log the order in which Awaitables were resolved/rejected, to ensure deterministic replay.- Random generators: Built-in helpers for generating stable UUIDs and random numbers.
Journaled actions
You can store the result of a (non-deterministic) operation in the Restate execution log (e.g. database requests, HTTP calls, etc). Restate replays the result instead of re-executing the operation on retries.
Here is an example of a database request for which the string response is stored in Restate:
- Java
- Kotlin
String output = ctx.run(JsonSerdes.STRING, () -> doDbRequest());
You can use Restate's built-in CoreSerdes
to serialize primitive types.
Have a look at the serialization docs for other options.
val output: String = ctx.runBlock { doDbRequest() }
By default, Kotlin serialization is used to serialize the result. Have a look at the serialization docs, for other options.
You cannot use the Restate context within a ctx.run
function.
This includes actions such as getting state, calling another service, and nesting other journaled actions.
Failures in ctx.run
have the same semantics as failures in the rest of your handler code.
By default, the ctx.run
function is retried infinitely on failure, unless you throw a terminal error.
Awaitable combinators
Operations such as calls, awakeables, and sleep return a Awaitable
.
The SDK provides combinators for working with Awaitable
.
Restate then logs the order in which they are resolved or rejected, to make them deterministic on replay.
- Java
- Kotlin
Await all creates an Awaitable
that awaits for all of the provided Awaitables to resolve.
The semantics are similar to CompleteableFuture.allOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
Awaitable.all(a1, a2, a3).await();
Await any creates an Awaitable
that awaits any of the provided Awaitables to resolve.
The semantics are similar to CompleteableFuture.anyOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
boolean res = (boolean) Awaitable.any(a1, a2, a3).await();
Await all
Creates an Awaitable
that awaits for all of the provided Awaitables to resolve.
The semantics are similar to CompleteableFuture.allOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
listOf(a1, a2, a3).awaitAll()
Await any
Creates an Awaitable
that awaits any of the provided Awaitables to resolve.
The semantics are similar to CompleteableFuture.anyOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
val res = Awaitable.any(a1, a2, a3).await() as Boolean// Or using the select statementval resSelect = select {a1.onAwait { it }a2.onAwait { it }a3.onAwait { it }}
Generating randoms
The SDK provides helper functions for the deterministic generation of UUIDs and random numbers. Restate seeds the random number generator with the invocation ID, so it always returns the same value on retries.
Generating UUIDs
You can use these UUIDs to generate stable idempotency keys, to deduplicate operations. For example, you can use this to let a payment service avoid duplicate payments during retries.
Do not use this in cryptographic contexts.
- Java
- Kotlin
UUID uuid = ctx.random().nextUUID();
val uuid: UUID = ctx.random().nextUUID()
Generating random numbers
This returns a new pseudorandom float within the range [0,1]
.
This is the equivalent of Java's Math.random()
but deterministically replayable.
You can use any of the methods of java.util.Random
to generate random numbers: for example, nextBoolean()
, nextLong()
, nextFloat()
, etc.
- Java
- Kotlin
int value = ctx.random().nextInt();
val value: Int = ctx.random().nextInt()