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.
DurableFuture
combinators: Log the order in which DurableFutures 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(String.class, () -> doDbRequest());
val output: String = ctx.runBlock { doDbRequest() }
Have a look at the serialization docs to learn how to serialize and deserialize your objects.
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 specify differently in the retry policy (see below) or throw a TerminalException
.
Run-block Retry Policies
You can configure the retry policy for the ctx.run
block:
- Java
- Kotlin
try {RetryPolicy myRunRetryPolicy =RetryPolicy.defaultPolicy().setInitialDelay(Duration.ofMillis(500)).setExponentiationFactor(2).setMaxDelay(Duration.ofSeconds(10)).setMaxAttempts(10).setMaxDuration(Duration.ofMinutes(5));ctx.run("my-run", myRunRetryPolicy, () -> writeToOtherSystem());} catch (TerminalException e) {// Handle the terminal error after retries exhausted// For example, undo previous actions (see sagas guide) and// propagate the error back to the caller}
try {val myRunRetryPolicy =RetryPolicy(initialDelay = 5.seconds,exponentiationFactor = 2.0f,maxDelay = 60.seconds,maxAttempts = 10,maxDuration = 5.minutes)ctx.runBlock("write", myRunRetryPolicy) { writeToOtherSystem() }} catch (e: TerminalException) {// Handle the terminal error after retries exhausted// For example, undo previous actions (see sagas guide) and// propagate the error back to the callerthrow e}
This way you can override the default retry behavior of your Restate service for specific operations.
Have a look at the RetryPolicy
JavaDoc and KotlinDoc for more information.
If you set a maximum number of attempts, then the ctx.run
block will fail with a TerminalException
once the retries are exhausted.
Have a look at the error handling docs and Sagas guide to learn how to handle these.
Durable Future combinators
Operations such as ctx.run
, calls, awakeables, and sleep return a DurableFuture
.
The SDK provides APIs to join DurableFuture
s, or wait for the first of them to complete.
Restate then logs the order in which they are resolved or rejected, to make them deterministic on replay.
Await all creates a DurableFuture
that awaits for all the provided DurableFutures to resolve.
The semantics are similar to CompleteableFuture.allOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
- Java
- Kotlin
DurableFuture.all(a1, a2, a3).await();
listOf(a1, a2, a3).awaitAll()
Select creates a DurableFuture
that returns on the first completed DurableFuture
of the provided ones.
The semantics are similar to CompleteableFuture.anyOf()
, but the outcome is stored in the Restate journal to be deterministically replayable.
- Java
- Kotlin
boolean res = Select.<Boolean>select().or(a1).or(a2).or(a3).await();
val resSelect =select {a1.onAwait { it }a2.onAwait { it }a3.onAwait { it }}.await()
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()