Skip to main content

Services

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

Application overview
  1. Restate Server: The server intercepts incoming requests and drives their execution till the end.
  2. Services: Contain the handlers which process incoming requests.
  3. Invocation: A request to execute a handler that is part of either a service, or a virtual object.

As you can see, handlers are bundled into services. Services run like regular RPC services (e.g. a NodeJS app in a Docker container). Services can be written in any language for which there is an SDK available (currently TypeScript and Java/Kotlin).

There are two types of services in Restate:

Services (plain)Virtual objects
Set of handlers durably executedSet of handlers durably executed
No associated K/V storeHandlers share K/V state; isolated per virtual object
No concurrency limitsSingle concurrent invocation per virtual object
Example use cases:
  • Microservice orchestration
  • To benefit from idempotency
  • Transformation functions
  • Sagas
Example use cases:
  • Atomic state machines
  • Digital twin
  • Locking mechanisms
  • Sequencing or ordering invocations

Services

Services expose a collection of handlers:

  • Restate makes sure that handlers run to completion, even in the presence of failures. Restate logs the results of actions in the system. Restate takes care of retries and recovers the handler to the point where it failed.
  • The handlers of services are independent and can be invoked concurrently.
  • Handlers use the regular code and control flow, no custom DSLs.
  • Service handlers don't have access to Restate's K/V store.

import * as restate from "@restatedev/restate-sdk";
import {Context} from "@restatedev/restate-sdk";
export const roleUpdateService = restate.service({
name: "roleUpdate",
handlers: {
applyRoleUpdate: async (ctx: Context, update: UpdateRequest) => {
const { userId, role, permissions } = update;
const success = await ctx.run(() => applyUserRole(userId, role));
if (!success) {
return;
}
for (const permission of permissions) {
await ctx.run(() => applyPermission(userId, permission));
}
}
}
});
restate.endpoint().bind(roleUpdateService).listen();

In the example, we use a Restate service to update different systems and to make sure all updates are applied. During retries, the service will not reapply the same update twice.

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.
  • 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 method execution.
  • 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. This can also be used for example to implement a locking mechanism or to ensure single writer to a database row.

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

Restate Server

In between the services, sits the Restate Server. It proxies invocations to the services and manages their lifecycle.

The Restate Server is written in Rust, to be self-contained and resource-efficient. It has an event-driven foundation. You can put it in the hot, latency-sensitive paths of your applications.

The main feature the Restate Server provides is Durable Execution. We dive into this in a later section.

The Restate Server runs as a single binary with zero dependencies. It runs with low operational overhead on any platform, also locally. To deploy the Restate Server, have a look at these deployment guides: