Restate provides three service types optimized for different use cases.

Service types comparison

Basic ServiceVirtual ObjectWorkflow
WhatIndependent stateless handlersStateful entity with a unique keyMulti-step processes that execute exactly-once per ID
StateNoneIsolated per object keyIsolated per workflow instance
ConcurrencyUnlimited parallel executionSingle writer per key (+ concurrent readers)Single run handler per ID (+ concurrent signals/queries)
Key FeaturesDurable execution, service callsBuilt-in K/V state, single-writer consistencyDurable promises, signals, lifecycle management
Best ForETL, sagas, parallelization, background jobsUser accounts, shopping carts, agents, state machines, stateful event processingApprovals, onboarding workflows, multi-step flows

Basic Service

Basic Services group related handlers as callable endpoints.
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)
        );
      }
    },
  },
});
Characteristics:
  • Use Durable Execution to run requests to completion
  • Scale horizontally with high concurrency
  • No shared state between requests
Use for: API calls, sagas, background jobs, task parallelization, ETL operations.

Virtual Object

Stateful entities identified by a unique key. Virtual Objects
const cartObject = restate.object({
  name: "ShoppingCart",
  handlers: {
    addItem: async (ctx: restate.ObjectContext, item: Item) => {
      const items = (await ctx.get<Item[]>("cart")) ?? [];
      items.push(item);
      ctx.set("cart", items);
      return items;
    },

    getTotal: restate.handlers.object.shared(
      async (ctx: restate.ObjectSharedContext) => {
        const items = (await ctx.get<Item[]>("cart")) ?? [];
        return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
      }
    ),
  },
});
Characteristics:
  • Use Durable Execution to run requests to completion
  • K/V state retained indefinitely and shared across requests
  • Horizontal scaling with state consistency:
    • At most one handler with write access can run at a time per object key. Mimicks a queue per object key.
    • Concurrent execution across different object keys
    • Concurrent execution of shared handlers (read-only)
Virtual Objects Use for: Modeling entities like user accounts, shopping carts, chat sessions, AI agents, state machines, or any business entity needing persistent state.

Workflow

Workflows orchestrate multi-step processes with guaranteed once-per-ID execution.
const signupWorkflow = restate.workflow({
  name: "UserSignup",
  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("create", () => createUserEntry({ userId, user }));

      const secret = ctx.rand.uuidv4();
      await ctx.run("mail", () => sendVerificationEmail({ user, secret }));

      const clickSecret = await ctx.promise<string>("email-link-clicked");
      return clickSecret === secret;
    },

    click: async (
      ctx: restate.WorkflowSharedContext,
      request: { secret: string }
    ) => {
      await ctx.promise<string>("email-link-clicked").resolve(request.secret);
    },
  },
});
Characteristics:
  • Use Durable Execution to run requests to completion
  • The run handler executes exactly once per workflow ID
  • Other handlers run concurrently with the run handler to signal, query state, or wait for events
  • Optimized APIs for workflow interaction and lifecycle management
Use for: Processes requiring interaction capabilities like approval flows, user onboarding, multi-step transactions, and complex orchestration.

Choosing the right service type

Start with Basic Services for most business logic, data processing, and API integrations. Use Virtual Objects to model stateful entities. Use Workflows for multi-step processes that execute exactly-once and require interaction. You can combine these service types within the same application for different aspects of your business logic.

Deployments, Endpoints, and Versions

Services deploy behind endpoints. Multiple services can bind to the same endpoint.
restate.serve({
  services: [subscriptionService, cartObject, signupWorkflow],
  port: 9080,
});
Services run on your preferred platform: serverless (AWS Lambda), containers (Kubernetes), or dedicated servers. Restate handles versioning through immutable deployments where each deployment represents a specific, unchangeable version of your service code. After deploying your services to an endpoint, you must register that endpoint with Restate so it can discover and route requests to it:
restate deployments register http://my-service:9080
When you update your services, you deploy the new version to a new endpoint and register it with Restate, which automatically routes new requests to the latest version while existing requests continue on their original deployment until completion. See deployment and versioning docs for details.