Skip to main content

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.

Just code
Get workflow-like semantics for any function: retries, recovery of progress,... Restate provides a distributed, durable version of familiar building blocks: timers, promises, signals,...
Low latency
Restate is built from the ground up to serve low-latency workflows. You can now put workflows in the latency-sensitive path of user interactions.
Deploy on FaaS
Run (parts of) your workflow on AWS Lambda. Restate lets your Lambda function suspend whenever its waiting on inputs.

Workflows with Restate

Implement the run function of your workflow, using the Restate SDK.

Run once, idempotently

A workflow runs exactly one time. Restate makes sure that duplicate requests do not lead to duplicate execution.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Persist progress

Store results of intermediate steps, interaction with external systems, or non-deterministic actions. Restate makes sure that on retries, the code does not get re-executed and the previous result is returned. This lets you execute the steps of your workflows durably.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

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.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Query workflow state

Retrieve the current state of the workflow from within other handlers and expose it to external clients.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Wait on external signals

Make Promises/Futures resilient by registering them in Restate. Share them and wait until other functions resolve them.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Signal in-flight workflows

Notify the workflow of external signals, callbacks or Kafka events. Resolve or reject shared promises on which the workflow is waiting. The workflow handles the outcome.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Flexible failure handling

Implement sagas and compensations in code, as per your requirements.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

Run once, idempotently

A workflow runs exactly one time. Restate makes sure that duplicate requests do not lead to duplicate execution.

Persist progress

Store results of intermediate steps, interaction with external systems, or non-deterministic actions. Restate makes sure that on retries, the code does not get re-executed and the previous result is returned. This lets you execute the steps of your workflows durably.

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.

Query workflow state

Retrieve the current state of the workflow from within other handlers and expose it to external clients.

Wait on external signals

Make Promises/Futures resilient by registering them in Restate. Share them and wait until other functions resolve them.

Signal in-flight workflows

Notify the workflow of external signals, callbacks or Kafka events. Resolve or reject shared promises on which the workflow is waiting. The workflow handles the outcome.

Flexible failure handling

Implement sagas and compensations in code, as per your requirements.

user_sign_up_flow.ts

const signUpWorkflow = restate.workflow({
name: "sign-up-workflow",
handlers: {
run: async (ctx: WorkflowContext, user: User) => {
const { id, name, email } = user;
ctx.set("stage", "Creating User");
await ctx.run(() => createUserEntry({ id, name }));
ctx.set("stage", "Email Verification");
const secret = ctx.rand.uuidv4();
await ctx.run(() => sendEmailWithLink({ email, secret }));
const clickSecret = await ctx.promise<string>("email-link");
if (clickSecret !== secret) {
ctx.set("stage", `Verification failed`);
throw new TerminalError("Wrong secret from email link");
}
ctx.set("stage", "User verified");
return true;
},
getStage: (ctx: WorkflowSharedContext) => ctx.get("stage"),
approveEmail: async (ctx: WorkflowSharedContext, secret: string) => {
await ctx.promise<string>("email-link").resolve(secret);
},
rejectEmail: async (ctx: WorkflowSharedContext) => {
await ctx.promise<string>("email-link").reject("Abort verification");
},
}
});

LOW-LATENCY

Restate’s event-driven foundation built in Rust lets you put workflows in the latency-sensitive path of user interaction.

DURABLE EXECUTION

Restate handles retries and recovers your code to the exact point before the crash. No more coarse per-step retries. State changes take part in durable execution, so the state never gets out of sync.

Latch on to a workflow

A workflow runs exactly one time. If the caller loses the connection to the workflow, he can latch on again to retrieve the result.

submit.ts

import * as clients from "@restatedev/restate-sdk-clients";
import {SignUpWorkflow, User} from "./signup_workflow";
const rs = clients.connect({url: "http://localhost:8080"});
const signUpWorkflow: SignUpWorkflow = {name: "sign-up-workflow"};
async function submit(user: User){
await rs.workflowClient(signUpWorkflow, user.id)
.workflowSubmit(user);
// do something else, with workflow running in the background
// attach back to the workflow
const success = await rs.workflowClient(signUpWorkflow, user.id)
.workflowAttach();
}

Latch on to a workflow

A workflow runs exactly one time. If the caller loses the connection to the workflow, he can latch on again to retrieve the result.

submit.ts

import * as clients from "@restatedev/restate-sdk-clients";
import {SignUpWorkflow, User} from "./signup_workflow";
const rs = clients.connect({url: "http://localhost:8080"});
const signUpWorkflow: SignUpWorkflow = {name: "sign-up-workflow"};
async function submit(user: User){
await rs.workflowClient(signUpWorkflow, user.id)
.workflowSubmit(user);
// do something else, with workflow running in the background
// attach back to the workflow
const success = await rs.workflowClient(signUpWorkflow, user.id)
.workflowAttach();
}

Workflows as regular, lightweight functions

Restate’s workflow functionality is integrated in its core.
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.

Workflows as regular, lightweight functions

What you can build with Workflows and Restate

User sign-up workflow

Create user in the system, wait until email confirmation, schedule a reminder, send welcome email, etc.

Order processing and logistics

Handle the payment, request the order preparation, wait for driver acceptance callback, etc.

Infrastructure provisioning

Go through a set of steps to provision a setup. Retry until resources are up, handle timeouts, rollbacks, etc.

Workflow interpreters

Dynamically compose workflows based on user input. For example, an image transformer that lets users specify the steps to be taken.

Wondering about a specific use case?

Let’s brainstorm together on Discord

Developer Resources

Blog post

We replaced 400 lines of StepFunctions ASL with 40 lines of TypeScript by making Lambdas suspendable.

Docs

Read the docs to learn more.

Need help?

Join the Restate Discord channel