Workflows-as-code
Lightweight, flexible, durable.
Combine the resiliency of workflows with the speed and flexibility of regular functions. Restate orchestrates and manages their execution till completion, whether it’s millis or months.
Familiar programming model
Low latency
Support for serverless
Workflows with Restate
Use Restate's durable building blocks to implement workflows that are resilient to failures and recoverable.
Durable building blocks
The Restate SDK gives you access to durable building blocks that you can use to build workflows: RPC calls, events, timers, promises, signals, and more.
Durable building blocks
The Restate SDK gives you access to durable building blocks that you can use to build workflows: RPC calls, events, timers, promises, signals, and more.
Persist progress
Restate logs the progress your code makes and recovers it in case of a crash. This way, you can build workflows that are durable and resilient to failures.
Persist progress
Restate logs the progress your code makes and recovers it in case of a crash. This way, you can build workflows that are durable and resilient to failures.
Durable promises and signals
Make Promises/Futures resilient by registering them in Restate. Share them and wait until other functions resolve them.
Durable promises and signals
Make Promises/Futures resilient by registering them in Restate. Share them and wait until other functions resolve them.
- TypeScript
- Java
- Kotlin
- Go
- Python
const signupWorkflow = restate.workflow({name: "user-signup",handlers: {run: async (ctx: restate.WorkflowContext,user: { name: string; email: string },) => {// workflow ID = user ID; workflow runs once per userconst userId = ctx.key;await ctx.run(() => createUserEntry(user));const secret = ctx.rand.uuidv4();await ctx.run(() => sendEmailWithLink({ userId, user, secret }));const clickSecret = await ctx.promise<string>("link-clicked");return clickSecret === secret;},click: async (ctx: restate.WorkflowSharedContext,request: { secret: string },) => {await ctx.promise<string>("link-clicked").resolve(request.secret);},},});
@Workflowpublic class SignupWorkflow {private static final DurablePromiseKey<String> LINK_CLICKED =DurablePromiseKey.of("link_clicked", JsonSerdes.STRING);@Workflowpublic boolean run(WorkflowContext ctx, User user) {// workflow ID = user ID; workflow runs once per userString userId = ctx.key();ctx.run(() -> createUserEntry(user));String secret = ctx.random().nextUUID().toString();ctx.run(() -> sendEmailWithLink(userId, user, secret));String clickSecret = ctx.promise(LINK_CLICKED).awaitable().await();return clickSecret.equals(secret);}@Sharedpublic void click(SharedWorkflowContext ctx, String secret) {ctx.promiseHandle(LINK_CLICKED).resolve(secret);}public static void main(String[] args) {RestateHttpEndpointBuilder.builder().bind(new SignupWorkflow()).buildAndListen();}}
@Workflowclass SignupWorkflow {companion object {private val LINK_CLICKED = KtDurablePromiseKey.json<String>("link_clicked")}@Workflowsuspend fun run(ctx: WorkflowContext, user: User): Boolean {// workflow ID = user ID; workflow runs once per userval userId = ctx.key()ctx.runBlock { createUserEntry(user) }val secret = ctx.random().nextUUID().toString()ctx.runBlock { sendEmailWithLink(userId, user, secret) }val clickSecret: String = ctx.promise(LINK_CLICKED).awaitable().await()return clickSecret == secret}@Sharedsuspend fun click(ctx: SharedWorkflowContext, secret: String) {ctx.promiseHandle(LINK_CLICKED).resolve(secret)}}fun main() {RestateHttpEndpointBuilder.builder().bind(SignupWorkflow()).buildAndListen()}
type SignupWorkflow struct{}func (SignupWorkflow) Run(ctx restate.WorkflowContext, user User) (bool, error) {// workflow ID = user ID; workflow runs once per useruserId := restate.Key(ctx)if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, CreateUserEntry(user)}); err != nil {return false, err}secret := restate.Rand(ctx).UUID().String()if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, SendEmailWithLink(userId, user, secret)}); err != nil {return false, err}clickSecret, err := restate.Promise[string](ctx, "link-clicked").Result()if err != nil {return false, err}return clickSecret == secret, nil}func (SignupWorkflow) Click(ctx restate.WorkflowSharedContext, secret string) error {return restate.Promise[string](ctx, "link-clicked").Resolve(secret)}func main() {if err := server.NewRestate().Bind(restate.Reflect(SignupWorkflow{})).Start(context.Background(), ":9080"); err != nil {log.Fatal(err)}}
user_signup = Workflow("user-signup")@user_signup.main()async def run(ctx: WorkflowContext, user: User) -> bool:user_id = ctx.key()await ctx.run("create_user", lambda: create_user_entry(user))secret = await ctx.run("secret", lambda: str(uuid.uuid4()))await ctx.run("send_email", lambda: send_email_with_link(user_id, user.email, secret))click_secret = await ctx.promise("link_clicked").value()return click_secret == secret@user_signup.handler()async def click(ctx: WorkflowSharedContext, secret: str):await ctx.promise("link_clicked").resolve(secret)app = restate.app(services=[user_signup])
Workflows as regular, lightweight functions
Restate is a single binary and its workflow functionality is integrated in its core.
Your workflows run like any other function in your infrastructure: on K8S, FaaS, or mix-and-match.
No need to spin up extra infrastructure or dedicated workers.
Invoke idempotently and latch on
A workflow runs exactly one time. Restate makes sure that duplicate requests do not lead to duplicate execution.
If the caller loses the connection to the workflow, he can latch on again to retrieve the result.
- TypeScript
- Java
- Kotlin
- Go
- Python
// import * as clients from "@restatedev/restate-sdk-clients";const restateClient = clients.connect({ url: "http://localhost:8080" });await restateClient.workflowClient<SignUpWorkflow>({ name: "user-signup" }, user.id).workflowSubmit(user);// do something else, with workflow running in the background// attach back to the workflowconst result = await restateClient.workflowClient<SignUpWorkflow>({ name: "user-signup" }, user.id).workflowAttach();
// import dev.restate.sdk.client.Client;Client restateClient = Client.connect("http://localhost:8080");SignupWorkflowClient.fromClient(restateClient, user.id()).submit(user);// do something else, with workflow running in the background// attach back to the workflowboolean result =SignupWorkflowClient.fromClient(restateClient, user.id()).workflowHandle().attach();
// import dev.restate.sdk.client.Client;val restateClient = Client.connect("http://localhost:8080")SignupWorkflowClient.fromClient(restateClient, user.id).submit(user)// do something else, with workflow running in the background// attach back to the workflowval result =SignupWorkflowClient.fromClient(restateClient, user.id).workflowHandle().attach()
const RESTATE_URL = "http://localhost:8080"client := &http.Client{}url := fmt.Sprintf("%s/SignupWorkflow/%s/Run/Send", RESTATE_URL, userId)userData, _ := json.Marshal(user)req, err := http.NewRequest("POST", url, bytes.NewBuffer(userData))if err != nil {return err}req.Header.Set("Content-Type", "application/json")resp, err := client.Do(req)if err != nil {return err}defer resp.Body.Close()// ... do other things while the task is being processed ...// Later on, you can retrieve the result of the task (possibly in a different process)attachUrl := fmt.Sprintf("%s/restate/workflow/SignupWorkflow/%s/attach", RESTATE_URL, userId)resp, err = http.DefaultClient.Get(attachUrl)if err != nil {return err}defer resp.Body.Close()// ... Process the result ...
restate = "http://localhost:8080"# 1. Submit the workflowuser_id = "myUser123"requests.post(f'${restate}/signupWorkflow/${user_id}/run/send',json = {"name": "Pete"},headers = {"Content-Type": "application/json"})# Do something else, with workflow running in the background# 2. Attach back to the workflowresponse = requests.get(f'${restate}/restate/workflow/signupWorkflow/${user_id}/attach')
LOW-LATENCY
Restate’s event-driven foundation built in Rust lets you put workflows in the latency-sensitive path of user interaction.
Learn more
DURABLE EXECUTION
Restate handles retries and recovers your code to the exact point before the crash. State changes take part in durable execution, so the state never gets out of sync.
Learn more
Flexible, Stateful Workflows with Restate
Flexible logic and failure handling
Restate guarantees code runs to completion. This makes it easy to implement resilient sagas in a simple try-catch block.
Track all rollback actions in a list and run them on unrecoverable failures.
Restate takes care of retries and recovery and makes sure all compensations run.
Flexible logic and failure handling
Restate guarantees code runs to completion. This makes it easy to implement resilient sagas in a simple try-catch block.
Track all rollback actions in a list and run them on unrecoverable failures.
Restate takes care of retries and recovery and makes sure all compensations run.
Queryable workflow state
Use Restate’s built-in key-value store to store workflow state. Restate guarantees that it is consistent and persistent, since state updates are tracked together with the rest of the execution progress.
You can retrieve the current state of the workflow from within other handlers and expose it to external clients.
Queryable workflow state
Use Restate’s built-in key-value store to store workflow state. Restate guarantees that it is consistent and persistent, since state updates are tracked together with the rest of the execution progress.
You can retrieve the current state of the workflow from within other handlers and expose it to external clients.
Stateful, serverless workflows
You can run stateful workflows on serverless infrastructure, like AWS Lambda. Restate attaches the state to the request. Your handlers work on local state
Stateful, serverless workflows
You can run stateful workflows on serverless infrastructure, like AWS Lambda. Restate attaches the state to the request. Your handlers work on local state
- TypeScript
- Java
- Kotlin
- Go
- Python
const subscriptionWorkflow = restate.workflow({name: "SubscriptionWorkflow",handlers: {run: async (ctx: restate.WorkflowContext, req: SubscriptionRequest) => {const compensations = [];try {const paymentId = ctx.rand.uuidv4();compensations.push(() => removeRecurringPayment(paymentId))await ctx.run(() => createRecurringPayment(req.creditCard, paymentId));ctx.set("status", "payment_success");for (const subscription of req.subscriptions) {compensations.push(() => removeSubscription(req.userId, subscription))await ctx.run(() => createSubscription(req.userId, subscription));}ctx.set("status", "subscribed");} catch (e) {if (e instanceof restate.TerminalError) {for (const compensation of compensations.reverse()) {await ctx.run(() => compensation());}ctx.set("status", "rolled_back");}throw e;}},},})export const awsLambdaHandler = restate.endpoint().bind(subscriptionWorkflow).handler();
@Workflowpublic class SubscriptionWorkflow {StateKey<String> STATUS = StateKey.of("status", STRING);@Workflowpublic void run(WorkflowContext ctx, SubscriptionRequest req) {List<Runnable> compensations = new ArrayList<>();try {var paymentId = ctx.random().nextUUID().toString();compensations.add(() -> removeRecurringPayment(paymentId));ctx.run(STRING, () -> createRecurringPayment(req.creditCard(), paymentId));ctx.set(STATUS, "payment_success");for (String subscription : req.subscriptions()) {compensations.add(() -> removeSubscription(req.userId(), subscription));ctx.run(() -> createSubscription(req.userId(), subscription));}ctx.set(STATUS, "subscribed");} catch (TerminalException e) {for (Runnable compensation : compensations) {ctx.run(() -> compensation.run());}ctx.set(STATUS, "rolled_back");throw e;}}}class MyLambdaHandler extends BaseRestateLambdaHandler {@Overridepublic void register(RestateLambdaEndpointBuilder builder) {builder.bind(new SubscriptionWorkflow());}}
@Workflowclass SubscriptionWorkflow {companion object {val STATUS = KtStateKey.json<String>("status")}@Workflowsuspend fun run(ctx: WorkflowContext, req: SubscriptionRequest) {val compensations: MutableList<suspend () -> Unit> = mutableListOf()try {val paymentId = ctx.random().nextUUID().toString()compensations.add { removeRecurringPayment(paymentId) }ctx.runBlock { createRecurringPayment(req.creditCard, paymentId) }ctx.set(STATUS, "payment_success")for (subscription in req.subscriptions) {compensations.add { removeSubscription(req.userId, subscription) }ctx.runBlock { createSubscription(req.userId, subscription) }}ctx.set(STATUS, "subscribed")} catch (e: TerminalException) {compensations.reversed().forEach { ctx.runBlock { it() } }ctx.set(STATUS, "rolled_back")throw TerminalException("Subscription failed")}}}class MyLambdaHandler : BaseRestateLambdaHandler() {override fun register(builder: RestateLambdaEndpointBuilder) {builder.bind(SubscriptionWorkflow())}}
type SubscriptionWorkflow struct{}func (SubscriptionWorkflow) Run(ctx restate.WorkflowContext, req SubscriptionRequest) error {compensations := make([]func() error, 0, len(req.Subscriptions)+1)handleError := func(err error) error {if restate.IsTerminalError(err) {for _, compensation := range compensations {if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return restate.Void{}, compensation()}); err != nil {return err}}}restate.Set(ctx, "status", "rolled_back")return err}paymentId := restate.Rand(ctx).UUID().String()compensations = append(compensations, func() error {return RemoveRecurringPayment(req.CreditCard, paymentId)})if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return CreateRecurringPayment(req.CreditCard, paymentId)}); err != nil {return handleError(err)}restate.Set(ctx, "status", "payment_success")for _, subscription := range req.Subscriptions {compensations = append(compensations, func() error {return RemoveSubscription(req.UserID, subscription)})if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) {return CreateSubscription(req.UserID, subscription)}); err != nil {return handleError(err)}}restate.Set(ctx, "status", "subscribed")return nil}func main() {handler, err := server.NewRestate().Bind(restate.Reflect(SubscriptionWorkflow{})).Bidirectional(false).LambdaHandler()if err != nil {log.Fatal(err.Error())}lambda.Start(handler)}
subscription_workflow = Workflow("SubscriptionWorkflow")@subscription_workflow.main()async def run(ctx: WorkflowContext, req: SubscriptionRequest):compensations = []try:payment_id = await ctx.run("payment id", lambda: str(uuid.uuid4()))compensations.append(lambda: remove_recurring_payment(payment_id))await ctx.run("recurring payment",lambda: create_recurring_payment(req.credit_card, payment_id))ctx.set("status", "payment_success");for subscription in req.subscriptions:compensations.append(lambda: remove_subscription(req.user_id, subscription))await ctx.run("subscription",lambda: create_subscription(req.user_id, subscription))ctx.set("status", "subscribed");except TerminalError as e:for compensation in reversed(compensations):await ctx.run("run compensation", compensation)ctx.set("status", "rolled_back");raise eaws_lambda_handler = restate.app([subscription_workflow])
Detailed Observability
Understand what is happening in your workflows, by using the UI, the CLI, and the built-in tracing.
Debug failing workflows, inspect the K/V state, and manage deployments.

What you can build with Workflows and Restate
Infrastructure provisioning
Go through a set of steps to provision a setup. Retry until resources are up, handle timeouts, rollbacks, etc.