Expand description
§Restate Rust SDK
Restate is a system for easily building resilient applications. This crate is the Restate SDK for writing Restate services using Rust.
§New to Restate?
If you are new to Restate, we recommend the following resources:
- Learn about the concepts of Restate
- Use cases:
- Quickstart
- Do the Tour of Restate to try out the APIs
§Features
Have a look at the following SDK capabilities:
- SDK Overview: Overview of the SDK and how to implement services, virtual objects, and workflows.
- Service Communication: Durable RPC and messaging between services (optionally with a delay).
- Journaling Results: Persist results in Restate’s log to avoid re-execution on retries
- State: read and write: Store and retrieve state in Restate’s key-value store
- Scheduling & Timers: Let a handler pause for a certain amount of time. Restate durably tracks the timer across failures.
- Awakeables: Durable Futures to wait for events and the completion of external tasks.
- Error Handling: Restate retries failures infinitely. Use
TerminalError
to stop retries. - Serialization: The SDK serializes results to send them to the Server.
- Serving: Start an HTTP server to expose services.
§SDK Overview
The Restate Rust SDK lets you implement durable handlers. Handlers can be part of three types of services:
- Services: a collection of durable handlers
- Virtual Objects: an object consists of a collection of durable handlers and isolated K/V state. Virtual Objects are useful for modeling stateful entities, where at most one handler can run at a time per object.
- Workflows: Workflows have a
run
handler that executes exactly once per workflow instance, and executes a set of steps durably. Workflows can have other handlers that can be called multiple times and interact with the workflow.
§Services
Services and their handlers are defined as follows:
// The prelude contains all the imports you need to get started
use restate_sdk::prelude::*;
// Define the service using Rust traits
#[restate_sdk::service]
trait MyService {
async fn my_handler(greeting: String) -> Result<String, HandlerError>;
}
// Implement the service
struct MyServiceImpl;
impl MyService for MyServiceImpl {
async fn my_handler(&self, ctx: Context<'_>, greeting: String) -> Result<String, HandlerError> {
Ok(format!("{greeting}!"))
}
}
// Start the HTTP server to expose services
#[tokio::main]
async fn main() {
HttpServer::new(Endpoint::builder().bind(MyServiceImpl.serve()).build())
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
.await;
}
- Specify that you want to create a service by using the
#[restate_sdk::service]
macro. - Create a trait with the service handlers.
- Handlers can accept zero or one parameter and return a
Result
. - The type of the input parameter of the handler needs to implement
Serialize
andDeserialize
. Seecrate::serde
. - The Result contains the return value or a
HandlerError
, which can be aTerminalError
or any other Rust’sstd::error::Error
. - The service handler can now be called at
<RESTATE_INGRESS_URL>/MyService/myHandler
. You can optionally override the handler name used via#[name = "myHandler"]
. More details on handler invocations can be found in the docs.
- Handlers can accept zero or one parameter and return a
- Implement the trait on a concrete type, for example on a struct.
- The first parameter of a handler after
&self
is always aContext
to interact with Restate. The SDK stores the actions you do on the context in the Restate journal to make them durable. - Finally, create an HTTP endpoint and bind the service(s) to it. Listen on the specified port (here 9080) for connections and requests.
§Virtual Objects
Virtual Objects and their handlers are defined similarly to services, with the following differences:
use restate_sdk::prelude::*;
#[restate_sdk::object]
pub trait MyVirtualObject {
async fn my_handler(name: String) -> Result<String, HandlerError>;
#[shared]
async fn my_concurrent_handler(name: String) -> Result<String, HandlerError>;
}
pub struct MyVirtualObjectImpl;
impl MyVirtualObject for MyVirtualObjectImpl {
async fn my_handler(
&self,
ctx: ObjectContext<'_>,
greeting: String,
) -> Result<String, HandlerError> {
Ok(format!("{} {}", greeting, ctx.key()))
}
async fn my_concurrent_handler(
&self,
ctx: SharedObjectContext<'_>,
greeting: String,
) -> Result<String, HandlerError> {
Ok(format!("{} {}", greeting, ctx.key()))
}
}
#[tokio::main]
async fn main() {
HttpServer::new(
Endpoint::builder()
.bind(MyVirtualObjectImpl.serve())
.build(),
)
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
.await;
}
- Specify that you want to create a Virtual Object by using the
#[restate_sdk::object]
macro. - The first argument of each handler must be the
ObjectContext
parameter. Handlers with theObjectContext
parameter can write to the K/V state store. Only one handler can be active at a time per object, to ensure consistency. - You can retrieve the key of the object you are in via [
ObjectContext.key
]. - If you want to have a handler that executes concurrently to the others and doesn’t have write access to the K/V state, add
#[shared]
to the handler definition in the trait. Shared handlers need to use theSharedObjectContext
. You can use these handlers, for example, to read K/V state and expose it to the outside world, or to interact with the blocking handler and resolve awakeables etc.
§Workflows
Workflows are a special type of Virtual Objects, their definition is similar but with the following differences:
use restate_sdk::prelude::*;
#[restate_sdk::workflow]
pub trait MyWorkflow {
async fn run(req: String) -> Result<String, HandlerError>;
#[shared]
async fn interact_with_workflow() -> Result<(), HandlerError>;
}
pub struct MyWorkflowImpl;
impl MyWorkflow for MyWorkflowImpl {
async fn run(&self, ctx: WorkflowContext<'_>, req: String) -> Result<String, HandlerError> {
//! implement workflow logic here
Ok(String::from("success"))
}
async fn interact_with_workflow(&self, ctx: SharedWorkflowContext<'_>) -> Result<(), HandlerError> {
//! implement interaction logic here
//! e.g. resolve a promise that the workflow is waiting on
Ok(())
}
}
#[tokio::main]
async fn main() {
HttpServer::new(Endpoint::builder().bind(MyWorkflowImpl.serve()).build())
.listen_and_serve("0.0.0.0:9080".parse().unwrap())
.await;
}
- Specify that you want to create a Workflow by using the
#[restate_sdk::workflow]
macro. - The workflow needs to have a
run
handler. - The first argument of the
run
handler must be theWorkflowContext
parameter. TheWorkflowContext
parameter is used to interact with Restate. Therun
handler executes exactly once per workflow instance. - The other handlers of the workflow are used to interact with the workflow: either query it, or signal it.
They use the
SharedWorkflowContext
to interact with the SDK. These handlers can run concurrently with the run handler and can still be called after the run handler has finished. - Have a look at the workflow docs to learn more.
Learn more about each service type here:
§Logging
This crate uses the tracing crate to emit logs, so you’ll need to configure a tracing subscriber to get logs. For example, to configure console logging using tracing_subscriber::fmt
:
#[tokio::main]
async fn main() {
//! To enable logging
tracing_subscriber::fmt::init();
// Start http server etc...
}
For more information, have a look at the tracing subscriber doc.
Next, have a look at the other SDK features.
Modules§
- Types exposing Restate functionalities to service handlers.
- This module contains the generated data structures from the service protocol manifest schema.
- Error Handling
- Serving
- Hyper integration.
- Prelude contains all the useful imports you need to get started with Restate.
- Serialization
Attribute Macros§
- Entry-point macro to define a Restate Virtual object.
- Entry-point macro to define a Restate Service.
- Workflows