Skip to main content

Services

This is what a Restate application looks like from a helicopter view:

Application overview
  1. Services: Restate services contain functions/handlers which process incoming requests and execute business logic. Services run like regular code in your infrastructure, for example a NodeJS/Java app in a Docker container or a Python function on AWS Lambda. Services embed the Restate SDK as a dependency, and their handlers use it to persist the progress they make. Services can be written in any language for which there is an SDK available: TypeScript, Java, Kotlin, Go, Python, and Rust.
  2. Restate Server: The server sits in front of your services, similar to a reverse proxy or message broker. It proxies incoming requests to the corresponding services and drives their execution till the end.
  3. Invocation: An invocation is a request to execute a handler.

There are three types of services in Restate:

Services (plain)Virtual objectsWorkflows
Set of handlers durably executedSet of handlers durably executedThe workflow run handler is durably executed a single time.
No associated K/V storeHandlers share K/V state; isolated per virtual objectK/V state isolated per workflow execution. Can only be set by the run handler.
No concurrency limits; unlimited scale-out on platforms like AWS Lambda.
  • To guard state consistency, only one handler with write access to the state can run at a time per virtual object: queue per key.
  • Handlers marked as shared don't have write access to state and can run concurrently to the exclusive handlers.
The run handler can run only a single time per workflow ID. Other handlers can run concurrently to interact with the workflow.
Example use cases:
  • Microservice orchestration
  • Sagas and distributed transactions
  • Exactly-once webhook callback processing
  • Idempotent requests
  • Parallelization, chaining API calls, and complex routing
  • Async task scheduling
Example use cases:
  • Stateful handlers and entities: e.g. shopping cart
  • Atomic, durable state machines: e.g. payment processing
  • Stateful agents, actors, and digital twins: e.g. AI chat sessions
  • Locking mechanisms: database writes
  • Stateful Kafka event processing: e.g. enrichment, joins
Example use cases:
  • Payments, order processing and logistics
  • Human-in-the-loop workflow: e.g. signup, email approval
  • Long-running tasks with failures/timeouts: e.g. infrastructure provisioning
  • Flexibility and dynamic routing: e.g. workflow interpreters

Services

Services expose a collection of handlers:

Restate makes sure that handlers run to completion, even in the presence of failures. Restate persists the results of actions and recovers them after failures.

Restate makes sure that handlers run to completion, even in the presence of failures. Restate persists the results of actions and recovers them after failures.

The handlers of services are independent and can be invoked concurrently.

The handlers of services are independent and can be invoked concurrently.

Handlers use regular code and control flow, no custom DSLs.

Handlers use regular code and control flow, no custom DSLs.

Handlers are exposed over HTTP. When the Restate Server receives a request, it sets up an HTTP2 connection with the service and streams events back and forth over this connection.

Alternatively, you can create a serverless function, like an AWS Lambda handler.

Handlers are exposed over HTTP. When the Restate Server receives a request, it sets up an HTTP2 connection with the service and streams events back and forth over this connection.

Alternatively, you can create a serverless function, like an AWS Lambda handler.

const subscriptionService = restate.service({
name: "SubscriptionService",
handlers: {
add: async (ctx: restate.Context, req: SubscriptionRequest) => {
const paymentId = ctx.rand.uuidv4();
const payRef = await ctx.run(() =>
createRecurringPayment(req.creditCard, paymentId)
);
for (const subscription of req.subscriptions) {
await ctx.run(() =>
createSubscription(req.userId, subscription, payRef)
);
}
},
},
});
restate.endpoint().bind(subscriptionService).listen(9080);

Learn more:

Virtual objects

Virtual objects expose a set of handlers with access to K/V state stored in Restate.

A virtual object is uniquely identified and accessed by its key.

A virtual object is uniquely identified and accessed by its key.

Each virtual object has access to its own isolated K/V state, stored in Restate. The handlers of a virtual object can read and write to the state of the object. Restate delivers the state together with the request to the virtual object, so virtual objects have their state locally accessible without requiring any database connection or lookup. State is exclusive, and atomically committed with the handler execution.

Each virtual object has access to its own isolated K/V state, stored in Restate. The handlers of a virtual object can read and write to the state of the object. Restate delivers the state together with the request to the virtual object, so virtual objects have their state locally accessible without requiring any database connection or lookup. State is exclusive, and atomically committed with the handler execution.

When a handler is invoked, it can read and write to the state of the virtual object. To ensure consistent writes to the state, Restate provides concurrency guarantees: at most one handler can execute at a time for a given virtual object.

When a handler is invoked, it can read and write to the state of the virtual object. To ensure consistent writes to the state, Restate provides concurrency guarantees: at most one handler can execute at a time for a given virtual object.

If you want to allow concurrent reads to the state, you can mark a handler as a shared handler. This allows the handler to run concurrently with other handlers, but it cannot write to the state.

If you want to allow concurrent reads to the state, you can mark a handler as a shared handler. This allows the handler to run concurrently with other handlers, but it cannot write to the state.

export const greeterObject = restate.object({
name: "greeter",
handlers: {
greet: async (ctx: restate.ObjectContext, req: { greeting: string }) => {
const name = ctx.key;
let count = (await ctx.get<number>("count")) ?? 0;
count++;
ctx.set("count", count);
return `${req.greeting} ${name} for the ${count}-th time.`;
},
ungreet: async (ctx: restate.ObjectContext) => {
const name = ctx.key;
let count = (await ctx.get<number>("count")) ?? 0;
if (count > 0) {
count--;
}
ctx.set("count", count);
return `Dear ${name}, taking one greeting back: ${count}.`;
},
getGreetCount: handlers.object.shared(
async (ctx: restate.ObjectSharedContext) => {
return await ctx.get<number>("count") ?? 0;
}
),
},
});
restate.endpoint().bind(greeterObject).listen();

Learn more:

Workflows

A workflow is a special type of Virtual Object that can be used to implement a set of steps that need to be executed durably. Workflows have additional capabilities such as signaling, querying, additional invocation options, and a longer retention time in the CLI.

A workflow has a run handler that implements the workflow logic. The run handler runs exactly once per workflow ID (object).

A workflow has a run handler that implements the workflow logic. The run handler runs exactly once per workflow ID (object).

The run handler executes a set of durable steps/activities. These can either be:

  • Inline activities: run blocks, sleep, mutating K/V state,...
  • Calls to other handlers implementing the activities.

The run handler executes a set of durable steps/activities. These can either be:

  • Inline activities: run blocks, sleep, mutating K/V state,...
  • Calls to other handlers implementing the activities.

You can define other handlers in the same workflow that can run concurrently to the run handler and:

  • Query the workflow (get information out of it) by getting K/V state or awaiting promises that are resolved by the workflow.
  • Signal the workflow (send information to it) by resolving promises that the workflow waits on. For example, the click handler signals the workflow that the email link was clicked.

You can define other handlers in the same workflow that can run concurrently to the run handler and:

  • Query the workflow (get information out of it) by getting K/V state or awaiting promises that are resolved by the workflow.
  • Signal the workflow (send information to it) by resolving promises that the workflow waits on. For example, the click handler signals the workflow that the email link was clicked.
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 user
const 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);
},
},
});
restate.endpoint().bind(signupWorkflow).listen(9080);

Learn more:

Restate Server

The Restate Server sits like reverse-proxy or message broker in front of your services and proxies invocations to them.

The Restate Server is written in Rust, to be self-contained and resource-efficient. It has an event-driven foundation to suit low-latency requirements.

Restate Server

The Restate Server runs as a single binary with zero dependencies. It runs with low operational overhead on any platform, also locally. You can run the Restate Server in a highly-available configuration, with multiple instances behind a load balancer.

Restate is also available as a fully managed cloud service, if all you want is to use it and let us operate it. Contact our team for more information.

Learn more about the Restate Server: