# Cluster health Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/cluster_health/cluster-health schemas/openapi-admin.json get /cluster-health Get the cluster health. # Create deployment Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/deployment/create-deployment schemas/openapi-admin.json post /deployments Create deployment. Restate will invoke the endpoint to gather additional information required for registration, such as the services exposed by the deployment. If the deployment is already registered, this method will fail unless `force` is set to `true`. # Delete deployment Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/deployment/delete-deployment schemas/openapi-admin.json delete /deployments/{deployment} Delete deployment. Currently it's supported to remove a deployment only using the force flag # Get deployment Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/deployment/get-deployment schemas/openapi-admin.json get /deployments/{deployment} Get deployment metadata # List deployments Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/deployment/list-deployments schemas/openapi-admin.json get /deployments List all registered deployments. # Update deployment Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/deployment/update-deployment schemas/openapi-admin.json put /deployments/{deployment} Update deployment. Invokes the endpoint and replaces the existing deployment metadata with the discovered information. This is a dangerous operation that should be used only when there are failing invocations on the deployment that cannot be resolved any other way. Sense checks are applied to test that the new deployment is sufficiently similar to the old one. # Health check Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/health/health-check schemas/openapi-admin.json get /health Check REST API Health. # Cancel an invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/cancel-an-invocation schemas/openapi-admin.json patch /invocations/{invocation_id}/cancel Cancel the given invocation. Canceling an invocation allows it to free any resources it is holding and roll back any changes it has made so far, running compensation code. For more details, checkout https://docs.restate.dev/guides/sagas # Delete an invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/delete-an-invocation schemas/openapi-admin.json delete /invocations/{invocation_id} Use kill_invocation/cancel_invocation/purge_invocation instead. # Kill an invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/kill-an-invocation schemas/openapi-admin.json patch /invocations/{invocation_id}/kill Kill the given invocation. This does not guarantee consistency for virtual object instance state, in-flight invocations to other services, etc. # Purge an invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/purge-an-invocation schemas/openapi-admin.json patch /invocations/{invocation_id}/purge Purge the given invocation. This cleanups all the state for the given invocation. This command applies only to completed invocations. # Purge an invocation journal Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/purge-an-invocation-journal schemas/openapi-admin.json patch /invocations/{invocation_id}/purge-journal Purge the given invocation journal. This cleanups only the journal for the given invocation, retaining the metadata. This command applies only to completed invocations. # Restart as new invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/restart-as-new-invocation schemas/openapi-admin.json patch /invocations/{invocation_id}/restart-as-new Restart the given invocation as new. This will restart the invocation as a new invocation with a different invocation id. By using the 'from' query parameter, some of the partial progress can be copied over to the new invocation. # Resume an invocation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/invocation/resume-an-invocation schemas/openapi-admin.json patch /invocations/{invocation_id}/resume Resume the given invocation. In case the invocation is backing-off, this will immediately trigger the retry timer. If the invocation is suspended or paused, this will resume it. # OpenAPI specification Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/openapi/openapi-specification schemas/openapi-admin.json get /openapi # Get service Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service/get-service schemas/openapi-admin.json get /services/{service} Get a registered service. # Get service OpenAPI Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service/get-service-openapi schemas/openapi-admin.json get /services/{service}/openapi Get the service OpenAPI 3.1 contract. # List services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service/list-services schemas/openapi-admin.json get /services List all registered services. # Modify a service Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service/modify-a-service schemas/openapi-admin.json patch /services/{service} Modify a registered service configuration. NOTE: Service re-discovery will update the settings based on the service endpoint configuration. # Modify a service state Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service/modify-a-service-state schemas/openapi-admin.json post /services/{service}/state Modify service state # Get service handler Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service_handler/get-service-handler schemas/openapi-admin.json get /services/{service}/handlers/{handler} Get the handler of a service # List service handlers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/service_handler/list-service-handlers schemas/openapi-admin.json get /services/{service}/handlers List all the handlers of the given service. # Create subscription Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/subscription/create-subscription schemas/openapi-admin.json post /subscriptions Create subscription. # Delete subscription Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/subscription/delete-subscription schemas/openapi-admin.json delete /subscriptions/{subscription} Delete subscription. # Get subscription Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/subscription/get-subscription schemas/openapi-admin.json get /subscriptions/{subscription} Get subscription # List subscriptions Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/subscription/list-subscriptions schemas/openapi-admin.json get /subscriptions List all subscriptions. # Admin version information Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/admin-api/version/admin-version-information schemas/openapi-admin.json get /version Obtain admin version information. # AI Agent Quickstart Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/ai-quickstart Build and run your first AI agent with Restate and popular AI SDKs export const GitHubLink = ({url}) =>
{ e.target.style.color = '#6B7280'; e.target.style.backgroundColor = '#F9FAFB'; }} onMouseOut={e => { e.target.style.color = '#6B7280'; e.target.style.backgroundColor = 'transparent'; }}> View on GitHub
; This guide takes you through building your first AI agent with Restate and popular AI SDKs. We will run a simple weather agent that can answer questions about the weather using durable execution to ensure reliability. AI Agent Quickstart Select your AI SDK: **Prerequisites**: * [Node.js](https://nodejs.org/en/) >= v20 * OpenAI API key (get one at [OpenAI](https://platform.openai.com/)) Restate is a single self-contained binary. No external dependencies needed. ```shell theme={null} brew install restatedev/tap/restate-server restatedev/tap/restate ``` Start the server: ```shell theme={null} restate-server ``` Download prebuilt binaries from the [releases page](https://github.com/restatedev/restate/releases/latest): ```shell MacOS-x64 theme={null} BIN=/usr/local/bin && RESTATE_PLATFORM=x86_64-apple-darwin && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ sudo mv restate $BIN && \ sudo mv restate-server $BIN ``` ```shell MacOS-arm64 theme={null} BIN=/usr/local/bin && RESTATE_PLATFORM=aarch64-apple-darwin && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ sudo mv restate $BIN && \ sudo mv restate-server $BIN ``` ```shell Linux-x64 theme={null} BIN=$HOME/.local/bin && RESTATE_PLATFORM=x86_64-unknown-linux-musl && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ mv restate $BIN && \ mv restate-server $BIN ``` ```shell Linux-arm64 theme={null} BIN=$HOME/.local/bin && RESTATE_PLATFORM=aarch64-unknown-linux-musl && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ mv restate $BIN && \ mv restate-server $BIN ``` Start the server: ```shell theme={null} restate-server ``` ```shell theme={null} npm install --global @restatedev/restate-server@latest @restatedev/restate@latest ``` Start the server: ```shell theme={null} restate-server ``` Run the Restate Server: ```shell theme={null} docker run --name restate_dev --rm \ -p 8080:8080 -p 9070:9070 -p 9071:9071 \ --add-host=host.docker.internal:host-gateway \ docker.restate.dev/restatedev/restate:latest ``` Run CLI commands: ```shell theme={null} docker run -it --network=host \ docker.restate.dev/restatedev/restate-cli:latest \ invocations ls ``` Replace `invocations ls` with any CLI subcommand. You can find the Restate UI running on port 9070 (`http://localhost:9070`) after starting the Restate Server. Get the weather agent template for the [Vercel AI SDK](https://ai-sdk.dev/docs/foundations/overview) and Restate: ```shell theme={null} git clone https://github.com/restatedev/ai-examples.git && cd ai-examples/vercel-ai/template && npm install ``` Export your OpenAI key and run the agent: ```shell theme={null} export OPENAI_API_KEY=your_openai_api_key_here npm run dev ``` The weather agent is now listening on port 9080. Tell Restate where the service is running (`http://localhost:9080`), so Restate can discover and register the services and handlers behind this endpoint. You can do this via the UI (`http://localhost:9070`) or via: ```shell CLI theme={null} restate deployments register http://localhost:9080 ``` ```shell curl theme={null} curl localhost:9070/deployments --json '{"uri": "http://localhost:9080"}' ``` ```shell CLI theme={null} ❯ SERVICES THAT WILL BE ADDED: - agent Type: Service HANDLER INPUT OUTPUT run value of content-type 'application/json' value of content-type 'application/json' ✔ Are you sure you want to apply those changes? · yes ✅ DEPLOYMENT: SERVICE REV agent 1 ``` ```shell curl theme={null} { "id": "dp_17sztQp4gnEC1L0OCFM9aEh", "services": [ { "name": "Agent", "handlers": [ { "name": "run", "ty": "Shared", "input_description": "one of [\"none\", \"value of content-type 'application/json'\"]", "output_description": "value of content-type 'application/json'" } ], "ty": "Service", "deployment_id": "dp_17sztQp4gnEC1L0OCFM9aEh", "revision": 1, "public": true, "idempotency_retention": "1day" } ] } ``` If you run Restate with Docker, register `http://host.docker.internal:9080` instead of `http://localhost:9080`. When using [Restate Cloud](https://restate.dev/cloud), your service must be accessible over the public internet so Restate can invoke it. If you want to develop with a local service, you can expose it using our [tunnel](/deploy/server/cloud/#registering-restate-services-with-your-environment) feature. Invoke the agent via the Restate UI playground: go to `http://localhost:9070`, click on your service and then on playground. Restate UI Playground Or invoke via `curl`: ```shell theme={null} curl localhost:8080/agent/run --json '"What is the weather in Detroit?"' ``` Output: `The weather in Detroit is currently 17°C with misty conditions.`. The agent you just invoked uses Durable Execution to make agents resilient to failures. Restate persisted all LLM calls and tool execution steps, so if anything fails, the agent can resume exactly where it left off. We did this by using Restate's `durableCalls` middleware to persist LLM responses and using [Restate Context actions](/foundations/actions) (e.g. `ctx.run`) to make the tool executions resilient: ```ts expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/vercel-ai/template/src/app.ts?collapse_imports"} theme={null} async function weatherAgent(restate: restate.Context, prompt: string) { // The durableCalls middleware persists each LLM response in Restate, // so they can be restored on retries without re-calling the LLM const model = wrapLanguageModel({ model: openai("gpt-4o"), middleware: durableCalls(restate, { maxRetryAttempts: 3 }), }); const { text } = await generateText({ model, system: "You are a helpful agent that provides weather updates.", prompt, tools: { getWeather: tool({ description: "Get the current weather for a given city.", inputSchema: z.object({ city: z.string() }), execute: async ({ city }) => { // call tool wrapped as Restate durable step return await restate.run("get weather", () => fetchWeather(city)); }, }), }, stopWhen: [stepCountIs(5)], providerOptions: { openai: { parallelToolCalls: false } }, }); return text; } // create a Restate Service as the callable entrypoint // for our durable agent function const agent = restate.service({ name: "agent", handlers: { run: async (ctx: restate.Context, prompt: string) => { return weatherAgent(ctx, prompt); }, }, }); // Serve the entry-point via an HTTP/2 server restate.serve({ services: [agent], }); ``` The Invocations tab of the Restate UI shows us how Restate captured each LLM call and tool step in a journal: Restate UI Journal Entries Ask about the weather in Denver: ```shell theme={null} curl localhost:8080/agent/run --json '"What is the weather in Denver?"' ``` You can see in the service logs and in the Restate UI how each LLM call and tool step gets durably executed. We can see how the weather tool is currently stuck, because the weather API is down. Restate UI Durable Execution This was a mimicked failure. To fix the problem, remove the line `failOnDenver` from the `fetchWeather` function in the `utils.ts` file: ```ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/vercel-ai/template/src/utils/weather.ts#weather"} theme={null} export async function fetchWeather(city: string) { failOnDenver(city); const output = await fetchWeatherFromAPI(city); return parseWeatherResponse(output); } ``` Once you restart the service, the agent resumes at the weather tool call and successfully completes the request. **Next step:** Follow the [Tour of Agents](/tour/vercel-ai-agents) to learn how to build agents with Restate and Vercel AI SDK, OpenAI Agents SDK, etc. **Prerequisites**: * Python >= v3.12 * [uv](https://docs.astral.sh/uv/getting-started/installation/) * OpenAI API key (get one at [OpenAI](https://platform.openai.com/)) Restate is a single self-contained binary. No external dependencies needed. ```shell theme={null} brew install restatedev/tap/restate-server restatedev/tap/restate ``` Start the server: ```shell theme={null} restate-server ``` Download prebuilt binaries from the [releases page](https://github.com/restatedev/restate/releases/latest): ```shell MacOS-x64 theme={null} BIN=/usr/local/bin && RESTATE_PLATFORM=x86_64-apple-darwin && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ sudo mv restate $BIN && \ sudo mv restate-server $BIN ``` ```shell MacOS-arm64 theme={null} BIN=/usr/local/bin && RESTATE_PLATFORM=aarch64-apple-darwin && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ sudo mv restate $BIN && \ sudo mv restate-server $BIN ``` ```shell Linux-x64 theme={null} BIN=$HOME/.local/bin && RESTATE_PLATFORM=x86_64-unknown-linux-musl && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ mv restate $BIN && \ mv restate-server $BIN ``` ```shell Linux-arm64 theme={null} BIN=$HOME/.local/bin && RESTATE_PLATFORM=aarch64-unknown-linux-musl && \ curl -L --remote-name-all https://restate.gateway.scarf.sh/latest/restate-{server,cli}-$RESTATE_PLATFORM.tar.xz && \ tar -xvf restate-server-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-server-$RESTATE_PLATFORM/restate-server && \ tar -xvf restate-cli-$RESTATE_PLATFORM.tar.xz --strip-components=1 restate-cli-$RESTATE_PLATFORM/restate && \ chmod +x restate restate-server && \ mv restate $BIN && \ mv restate-server $BIN ``` Start the server: ```shell theme={null} restate-server ``` ```shell theme={null} npm install --global @restatedev/restate-server@latest @restatedev/restate@latest ``` Start the server: ```shell theme={null} restate-server ``` Run the Restate Server: ```shell theme={null} docker run --name restate_dev --rm \ -p 8080:8080 -p 9070:9070 -p 9071:9071 \ --add-host=host.docker.internal:host-gateway \ docker.restate.dev/restatedev/restate:latest ``` Run CLI commands: ```shell theme={null} docker run -it --network=host \ docker.restate.dev/restatedev/restate-cli:latest \ invocations ls ``` Replace `invocations ls` with any CLI subcommand. You can find the Restate UI running on port 9070 (`http://localhost:9070`) after starting the Restate Server. Get the weather agent template for the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) and Restate: ```shell theme={null} git clone https://github.com/restatedev/ai-examples.git && cd ai-examples/openai-agents/template ``` Export your OpenAI key and run the agent: ```shell theme={null} export OPENAI_API_KEY=your_openai_api_key_here uv run . ``` The weather agent is now listening on port 9080. Tell Restate where the service is running (`http://localhost:9080`), so Restate can discover and register the services and handlers behind this endpoint. You can do this via the UI (`http://localhost:9070`) or via: ```shell CLI theme={null} restate deployments register http://localhost:9080 ``` ```shell curl theme={null} curl localhost:9070/deployments --json '{"uri": "http://localhost:9080"}' ``` ```shell CLI theme={null} ❯ SERVICES THAT WILL BE ADDED: - Agent Type: Service HANDLER INPUT OUTPUT run value of content-type 'application/json' value of content-type 'application/json' ✔ Are you sure you want to apply those changes? · yes ✅ DEPLOYMENT: SERVICE REV Agent 1 ``` ```shell curl theme={null} { "id": "dp_17sztQp4gnEC1L0OCFM9aEh", "services": [ { "name": "Agent", "handlers": [ { "name": "run", "ty": "Shared", "input_description": "one of [\"none\", \"value of content-type 'application/json'\"]", "output_description": "value of content-type 'application/json'" } ], "ty": "Service", "deployment_id": "dp_17sztQp4gnEC1L0OCFM9aEh", "revision": 1, "public": true, "idempotency_retention": "1day" } ] } ``` If you run Restate with Docker, register `http://host.docker.internal:9080` instead of `http://localhost:9080`. When using [Restate Cloud](https://restate.dev/cloud), your service must be accessible over the public internet so Restate can invoke it. If you want to develop with a local service, you can expose it using our [tunnel](/deploy/server/cloud/#registering-restate-services-with-your-environment) feature. Invoke the agent via the Restate UI playground: go to `http://localhost:9070`, click on your service and then on playground. Restate UI Playground Or invoke via `curl`: ```shell theme={null} curl localhost:8080/agent/run --json '"What is the weather in Detroit?"' ``` Output: `The weather in Detroit is currently 17°C with misty conditions.` The agent you just invoked uses Durable Execution to make agents resilient to failures. Restate persisted all LLM calls and tool execution steps, so if anything fails, the agent can resume exactly where it left off. We did this by using the `DurableModelCalls` model provider to persist LLM responses and using [Restate Context actions](/foundations/actions) (e.g. `ctx.run`) to make the tool executions resilient: ```python expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/template/agent.py?collapse_imports"} theme={null} @function_tool(failure_error_function=raise_restate_errors) async def get_weather( wrapper: RunContextWrapper[restate.Context], req: WeatherRequest ) -> WeatherResponse: """Get the current weather for a given city.""" # Do durable steps using the Restate context restate_context = wrapper.context return await restate_context.run_typed("Get weather", fetch_weather, city=req.city) weather_agent = Agent[restate.Context]( name="WeatherAgent", instructions="You are a helpful agent that provides weather updates.", tools=[get_weather], ) agent_service = restate.Service("agent") @agent_service.handler() async def run(restate_context: restate.Context, message: str) -> str: result = await Runner.run( weather_agent, input=message, # Pass the Restate context to tools to make tool execution steps durable context=restate_context, # Choose any model and let Restate persist your calls run_config=RunConfig( model="gpt-4o", model_provider=DurableModelCalls(restate_context) ), ) return result.final_output ``` The Invocations tab of the Restate UI shows us how Restate captured each LLM call and tool step in a journal: Restate UI Journal Entries Ask about the weather in Denver: ```shell theme={null} curl localhost:8080/agent/run --json '"What is the weather in Denver?"' ``` You can see in the service logs and in the Restate UI how each LLM call and tool step gets durably executed. We can see how the weather tool is currently stuck, because the weather API is down. Restate UI Durable Execution This was a mimicked failure. To fix the problem, remove the line `fail_on_denver` from the `fetch_weather` function in the `utils.py` file: ```python {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/ai-examples/refs/heads/main/openai-agents/template/utils/utils.py#weather"} theme={null} async def fetch_weather(city: str) -> WeatherResponse: fail_on_denver(city) weather_data = await call_weather_api(city) return parse_weather_data(weather_data) ``` Once you restart the service, the agent resumes at the weather tool call and successfully completes the request. # Connecting services to Restate Cloud Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/cloud/connecting-services Learn how to connect your services to Restate Cloud. You can connect your services to Restate Cloud in several ways, depending on where they run: * If your [services run in private environments](#connecting-services-in-private-environments), by setting up a tunnel. * If your [services run on AWS Lambda](#connecting-aws-lambda-services), by giving Restate Cloud permission to invoke them. * By making your [services accessible over the public internet](#public-endpoint-with-request-signing), and using request signing. ## Connecting services in private environments You can connect services that run in a locked-down private environment to Restate Cloud using a secure tunnel. This allows you to expose your services to Restate Cloud without opening up ports to the public internet. The tunnel will also act as an authenticating proxy to Restate Cloud and make it appear as if your Restate Cloud environment is inside your private network. This allows you to use native access control mechanisms, like VPC Security Groups and Kubernetes network policies, to manage access to your environment. ### 1. Create a Restate Cloud environment Open the [Restate Cloud UI](https://cloud.restate.dev) and create a new environment. Note the environment id (`env_...`) and request signing key (under security -> HTTP services: `publickeyv1_...`), as you will need them later. Create a `"Full"` scope API key with a descriptive name for the tunnel client and copy the key for later. ### 2. Setup the CLI Next, we need to set up a CLI profile to connect to our Restate Cloud environment. Log in to Restate Cloud using the CLI: ```bash theme={null} restate cloud login ``` Set up a new CLI profile for your environment: ```bash theme={null} restate cloud env configure ``` Tell the CLI to use the new environment: ```bash theme={null} restate config use-env ``` ### 3. Run the tunnel The tunnel can be hosted as a cloud VM in your VPC, a sidecar to your service, or a dedicated pod in your container orchestrator. You can deploy multiple copies for redundancy. To run the tunnel: ```bash theme={null} # export RESTATE_ENVIRONMENT_ID=env_... # export RESTATE_BEARER_TOKEN=key_... # export RESTATE_TUNNEL_NAME=test-tunnel # export RESTATE_SIGNING_PUBLIC_KEY=publickeyv1_... # export RESTATE_CLOUD_REGION=eu docker run \ -e RESTATE_ENVIRONMENT_ID \ -e RESTATE_BEARER_TOKEN \ -e RESTATE_TUNNEL_NAME \ -e RESTATE_SIGNING_PUBLIC_KEY \ -e RESTATE_CLOUD_REGION \ -p 8080:8080 \ -p 9090:9090 \ -p 9070:9070 -it ghcr.io/restatedev/restate-cloud-tunnel-client:latest ``` * `RESTATE_ENVIRONMENT_ID` is the environment id (including the `env_` prefix). * `RESTATE_BEARER_TOKEN` is the API key you created in step 1. * `RESTATE_TUNNEL_NAME` is a name for the tunnel. Choose a unique DNS-friendly tunnel name, e.g. `prod-tunnel`. * `RESTATE_SIGNING_PUBLIC_KEY` is the public key you copied from the Cloud UI in step 1. * `RESTATE_CLOUD_REGION` is the region of your Restate Cloud environment, e.g. `eu` or `us`. * You can run `latest` or pin the current version, e.g. `0.4.0` * The health check URL is at `:9090/health` The tunnel client exposes the following ports * `9090` tunnel's own health status * `8080` Restate Ingress (authenticating proxy) * `9070` Restate Admin API (authenticating proxy) ### 4. Run a Restate service Follow the quickstart to [run a Restate service locally](/quickstart). ```bash TypeScript theme={null} restate example typescript-hello-world cd typescript-hello-world npm install npm run dev ``` ```bash Java theme={null} restate example java-hello-world-maven cd java-hello-world-maven mvn compile exec:java ``` ```bash Kotlin theme={null} restate example kotlin-hello-world cd kotlin-hello-world ./gradlew run ``` ```bash Python theme={null} restate example python-hello-world cd python-hello-world uv run . ``` ```Bash Go theme={null} restate example go-hello-world && cd go-hello-world go run . ``` ```bash Rust theme={null} restate example rust-hello-world cd rust-hello-world cargo run ``` ### 5. Register your service If your setup is correct, you can now register your service with the Restate Cloud environment: ```bash theme={null} restate deployments register https://tunnel..restate.cloud:9080///http// ``` * Use just the numeric environment id here (without the `env_` prefix). * The tunnel name must match the name provided to the tunnel client * The remote fqdn and port must resolve to the Restate service endpoint (e.g. `localhost:9080`) from the perspective of the tunnel client For example, if your service is running on `localhost:9080` and your environment id is `env_20d1231jyphzkm8` in region `eu`, you can register it like this: ```bash theme={null} restate deployments register https://tunnel.eu.restate.cloud:9080/20d1231jyphzkm8/test-tunnel/http/localhost/9080 ``` ### 6. Test your service You can now test your service by invoking it via Restate Cloud: ```bash TypeScript theme={null} curl localhost:8080/Greeter/greet --json '{"name": "Sarah"}' ``` ```bash Java theme={null} curl localhost:8080/Greeter/greet --json '{"name": "Sarah"}' ``` ```bash Kotlin theme={null} curl localhost:8080/Greeter/greet --json '{"name": "Sarah"}' ``` ```bash Python theme={null} curl localhost:8080/Greeter/greet --json '{"name": "Sarah"}' ``` ```Bash Go theme={null} curl localhost:8080/Greeter/Greet --json '"Sarah"' ``` ```bash Rust theme={null} curl localhost:8080/Greeter/greet --json '"Sarah"' ``` ### How does it work? * Restate CLI is communicating with your Restate environment at `https://*.eu.restate.cloud:9070` using your user ID token and asks the admin API to perform a discovery at the special tunnel URL. * The Restate Cloud end of the tunnel receives the request from your environment and forwards the traffic to the tunnel container. * The tunnel container is forwarding the traffic to the Restate service endpoint. Invocation overview ## Connecting AWS Lambda services To invoke services running on AWS Lambda, Restate Cloud needs to assume an AWS identity in the same account that the Lambda is deployed to. Create a new role that has permission to invoke your Lambda handlers, and give it the following trust policy. The Restate Cloud role is distinct from the Lambda function's execution role. The execution role is assumed by your function to perform its work. A dedicated invoker role is needed to grant Restate Cloud permission to invoke service handlers functions in your account, and nothing more. ```json IAM JSON Policy expandable theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::654654156625:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:PrincipalArn": "arn:aws:iam::654654156625:role/RestateCloud", "sts:ExternalId": "${ENVIRONMENT_ID}" } } }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::654654156625:root" }, "Action": "sts:TagSession" } ] } ``` Replace the `${ENVIRONMENT_ID}` placeholder with the environment ID can be found in the UI and in the output of `restate whoami`. This trust policy allows the Restate Cloud `us.restate.cloud` region principal to assume the role, but only on behalf of the specified environment ID. ```ts AWS CDK theme={null} const restateCloudRole = new iam.Role(this, "RestateCloudRole", { assumedBy: new iam.AccountPrincipal("654654156625") .withConditions({ "StringEquals": { "sts:ExternalId": environmentId, "aws:PrincipalArn": "arn:aws:iam::654654156625:role/RestateCloud", }, }), }); restateCloudRole.assumeRolePolicy!.addStatements( new iam.PolicyStatement({ principals: [new iam.AccountPrincipal("654654156625")], actions: ["sts:TagSession"], }), ); ``` When you use the [Restate CDK construct library](https://github.com/restatedev/cdk) to deploy Lambda handlers, the provided invoker role will automatically be granted access to invoke the corresponding functions. If you manage Restate service deployments some other way, you should ensure that the Restate Cloud invoker role is permitted to call the appropriate Lambda handler functions by allowing it to perform `lambda:InvokeFunction`. Use the `environmentId` variable to pass the environment ID can be found in the UI and in the output of `restate whoami`. This trust policy allows the Restate Cloud `us.restate.cloud` region principal to assume the role, but only on behalf of the specified environment. You can now register your Lambda through the new role: ```shell theme={null} restate deployments register --assume-role-arn ``` If something isn't working, the environment logs in the cloud UI may help you find the issue. **Securing your services:** If your Lambda has an appropriate trust policy as described above, you do not need to secure incoming requests any further. If you choose to however, the identity verification checks will work on Lambda endpoints as well. ## Public endpoint with request signing Restate can invoke your services over the public internet. For production use cases HTTPS must be used between Restate and your services. You can terminate TLS at the service endpoint directly, but it's likely easier to use a fronting load balancer like an AWS NLB. You must secure access to your service so that only Restate can call it. The easiest way to do this is with our native request identity feature. All requests to your service will be signed with a unique environment-specific private key. You can find the corresponding public key in the environment settings UI, under HTTP Services. It is safe to include this public key directly in your service code. Have a look at the SDK serving documentation to learn how for [TS](/develop/ts/serving#validating-request-identity), [Java, Kotlin](/develop/java/serving#validating-request-identity), [Python](/develop/python/serving#validating-request-identity), [Go](/develop/go/serving#validating-request-identity), and [Rust](https://docs.rs/restate-sdk/latest/restate_sdk/http_server/index.html#validating-request-identity). # Getting Started with Restate Cloud Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/cloud/getting-started Learn how to use Restate Cloud, the fully managed version of Restate. Learn more and sign up. Restate Cloud is a fully managed serverless version of Restate, with a suite of tools supporting a stellar local development experience, and security features like [request signing](#securing-your-services) and [IAM roles for AWS Lambda](#aws-lambda-services). This allows you to focus on your services, which you can deploy wherever you normally do, while Restate Cloud handles all aspects of availability and durability for your invocations, workflows, and state. ## Creating your first environment You can sign in to Cloud using an email address and password, or using Google or GitHub SSO. After signing up, you'll be prompted to create an account, and an environment. An environment is an instance of the Restate server that is unique to you, and managed by Restate. Accounts and environments must have a name comprising of lowercase alphanumeric characters or hyphens. For example, you can choose `dev` for your account and `my-environment` for your environment. You can start using your new environment straight away using the [CLI](/installation#running-restate-server--cli-locally): ```bash theme={null} # authenticate to Cloud restate cloud login # set up your CLI to talk to your Cloud environment restate cloud env configure # check that everything is working! restate whoami ``` At any time, you can switch your CLI back to point to a Restate server running on your machine with `restate config use-environment local` (see the [CLI config docs](/references/cli-config#)). ## Registering Restate services with your environment You can use your Cloud environment exactly as a self-hosted one; register services with `restate deployments register `. However, currently your services must be accessible over the public internet for Restate to be able to invoke them. If you want to develop using a service running on your local machine, you can expose it using our tunnel feature: ```bash theme={null} # expose localhost:9080 to Restate Cloud restate cloud env tunnel --local-port 9080 # copy the tunnel url from the output and register it to your environment restate deployments register tunnel://example:9081 ``` ## Invoking your services In the UI you can find the URL of your Restate environment. All requests must be authenticated using a Bearer token. We suggest that humans use their SSO token which is obtained by `restate cloud login`, and will be used automatically by the CLI. If you want to test invocations using `curl` as described in the [invoke docs](/services/invocation/http), you can use the tunnel command to expose the ingress port of the Restate server as if it was running on your machine: ```bash theme={null} restate cloud env tunnel --remote-port 8080 curl localhost:8080/MyService/myHandler ``` ### API tokens For programmatic invocation, awakeable completion, or admin API usage from scripts, CI pipelines, API gateways, or services that exist outside Restate, you can create an API key in the UI, selecting an appropriate role for your use case. ```bash theme={null} curl -H "Authorization: Bearer $MY_API_KEY" https://201hy10cd3h6426jy80tb32n6en.env.us.restate.cloud:8080/MyService/MyHandler curl -H "Authorization: Bearer $MY_API_KEY" https://201hy10cd3h6426jy80tb32n6en.env.us.restate.cloud:9070/deployments ``` To use the CLI programmatically with this token: ```bash theme={null} export RESTATE_HOST_SCHEME=https RESTATE_HOST=201hy10cd3h6426jy80tb32n6en.env.us.restate.cloud RESTATE_AUTH_TOKEN=$MY_API_KEY restate whoami ``` # AI-Assisted Development Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ai-assistant Developing Restate apps with AI coding agents. The Restate documentation site includes several features to help you leverage AI coding agents like Cursor and Claude Code in your development workflow. ## Contextual menu Each page of the Restate documentation has a contextual menu in the top right corner that provides quick access to various AI resources: Contextual menu You can use this to quickly hydrate your AI chat with the content of the current page, or to add the Restate docs MCP server to Cursor, or VSCode. ## Restate Docs as MCP server The Restate documentation is available as an MCP server, which you can add to Cursor or other AI coding agents that support MCP. You can either use the shortcuts in the [contextual menu](/develop/ai-assistant#contextual-menu) or follow the following guidelines: 1. Navigate to the `Connectors` page in the Claude settings. 2. Select `Add custom connector`. 3. Add the MCP server name and URL: `restate-docs`, `https://docs.restate.dev/mcp`. 4. Select `Add`. 5. When using Claude, select the attachments button (the plus icon). 6. Select your MCP server. To add the Restate documentation MCP server to Claude Code, run the following command in your terminal: ```shell theme={null} claude mcp add --transport http restate-docs https://docs.restate.dev/mcp ``` 1. Use `Command` + `Shift` + `P` (`Ctrl` + `Shift` + `P` on Windows) to open the command palette. 2. Search for `“Open MCP settings”`. 3. Select `Add custom MCP`. This will open the `mcp.json` file. 4. In `mcp.json`, configure your server: ```json theme={null} { "mcpServers": { "restate-docs": { "url": "https://docs.restate.dev/mcp" } } } ``` 1. Create a `.vscode/mcp.json` file. 2. In `mcp.json`, configure your server: ```json theme={null} { "servers": { "restate-docs": { "type": "http", "url": "https://docs.restate.dev/mcp" } } } ``` More information in the [Mintlify documentation](https://www.mintlify.com/docs/ai/model-context-protocol#using-your-mcp-server). ## AGENTS.md: Coding agent rules AGENTS.md is a simple markdown file for defining agent instructions. To improve the performance of coding agents when implementing Restate applications, add the following rules to your agent's context. * [TypeScript SDK AGENTS.md](/develop/ts/agents.md) * [Java SDK AGENTS.md](/develop/java/agents.md) * [Python SDK AGENTS.md](/develop/python/agents.md) * [Go SDK AGENTS.md](/develop/go/agents.md) ### Cursor Download the AGENTS.md file and put it [in the `.cursor/rules` folder](https://cursor.com/docs/context/rules#agentsmd) at the root of your project: ```shell Typescript theme={null} mkdir -p ./.cursor/rules && wget -O ./.cursor/rules/AGENTS.md https://docs.restate.dev/develop/ts/agents.md ``` ```shell Java theme={null} mkdir -p ./.cursor/rules && wget -O ./.cursor/rules/AGENTS.md https://docs.restate.dev/develop/java/agents.md ``` ```shell Python theme={null} mkdir -p ./.cursor/rules && wget -O ./.cursor/rules/AGENTS.md https://docs.restate.dev/develop/python/agents.md ``` ```shell Go theme={null} mkdir -p ./.cursor/rules && wget -O ./.cursor/rules/AGENTS.md https://docs.restate.dev/develop/go/agents.md ``` ### Claude Code Download the CLAUDE.md file and put it [in the `.claude` folder](https://www.anthropic.com/engineering/claude-code-best-practices) at the root of your project: ```shell Typescript theme={null} mkdir -p ./.claude && wget -O ./.claude/CLAUDE.md https://docs.restate.dev/develop/ts/agents.md ``` ```shell Java theme={null} mkdir -p ./.claude && wget -O ./.claude/CLAUDE.md https://docs.restate.dev/develop/java/agents.md ``` ```shell Python theme={null} mkdir -p ./.claude && wget -O ./.claude/CLAUDE.md https://docs.restate.dev/develop/python/agents.md ``` ```shell Go theme={null} mkdir -p ./.claude && wget -O ./.claude/CLAUDE.md https://docs.restate.dev/develop/go/agents.md ``` ## llms.txt and llms-full.txt The Restate documentation is available in markdown, for easy ingestion by LLMs. Add `.md` to any page’s URL to view a Markdown version. The documentation also includes [`llms.txt`](/llms.txt) (navigation structure) and [`llms-full.txt`](/llms-full.txt) (full documentation) files. # Code Generation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/code-generation Use proto for type-safe servers and clients. The Go SDK supports generating typed clients and server interfaces from [Protocol Buffer service definitions](https://protobuf.dev/programming-guides/proto3/#services). A full example can be found in the [SDK repository](https://github.com/restatedev/sdk-go/tree/main/examples/codegen). Code generation relies on the tool `protoc-gen-go-restate` which you can install using `go install github.com/restatedev/sdk-go/protoc-gen-go-restate@latest`, in addition to `protoc` which you can install from the [Protobuf compiler repo](https://github.com/protocolbuffers/protobuf#protobuf-compiler-installation). ## Defining a service To get started, create a Protocol Buffer definition file defining a service: ```proto service.proto {"CODE_LOAD::go/develop/codegen/proto/service.proto#service"} theme={null} package greeter; service Greeter { rpc SayHello (HelloRequest) returns (HelloResponse) {} } message HelloRequest { string name = 1; } message HelloResponse { string message = 1; } ``` You can compile this file into Go types and your Restate generated code with: ```shell theme={null} protoc --go_out=. --go_opt=paths=source_relative --go-restate_out=. --go-restate_opt=paths=source_relative proto/service.proto ``` This creates two files: `service.pb.go` containing Go types, and `service_restate.pb.go` which contains several useful objects: 1. `NewGreeterClient`, which returns an interface for making type-safe calls to the Greeter service. The default serialization for request and response data is the [canonical Protobuf JSON encoding](https://protobuf.dev/programming-guides/proto3/#json), but this is configurable. 2. `GreeterServer`, an interface for server code to implement. An implementor can be converted into a service definition with `NewGreeterServer`. 3. `UnimplementedGreeterServer`, an empty struct that implements `GreeterServer` by just returning 'not implemented' terminal errors for each method. You can choose to embed this struct into your own implementation struct, in which case new methods in the Protobuf definition will not cause compilation errors in your Go code, which can help with backwards compatibility. Both `NewGreeterClient` and `NewGreeterServer` accept option parameters which can be used to set a different serialization codec. For example, you can use Protobuf by providing `restate.WithProto` to both functions. ## Implementing the server Next, you can implement the `GreeterServer` interface and bind it to your Restate server. ```go {"CODE_LOAD::go/develop/codegen/codegen.go#server"} theme={null} type greeter struct { proto.UnimplementedGreeterServer } func (greeter) SayHello(ctx restate.Context, req *proto.HelloRequest) (*proto.HelloResponse, error) { return &proto.HelloResponse{ Message: fmt.Sprintf("%s!", req.Name), }, nil } func main() { if err := server.NewRestate(). Bind(proto.NewGreeterServer(greeter{})). Start(context.Background(), ":9080"); err != nil { log.Fatal(err) } } ``` ## Using the client `NewGreeterClient` will return a similar client to what you'd get from `restate.Service`, with additional type safety for method names and request/response types. ```go {"CODE_LOAD::go/develop/codegen/codegen.go#client"} theme={null} client := proto.NewGreeterClient(ctx) resp, err := client.SayHello().Request(&proto.HelloRequest{Name: "world"}) if err != nil { return err } ``` ## Defining Virtual Objects and Workflows To define a Virtual Object or Workflow, you will need to provide the option `dev.restate.sdk.go.service_type` when defining the service. This option is defined by the Go SDK's proto file [`dev/restate/sdk/go.proto`](https://github.com/restatedev/sdk-go/blob/main/proto/dev/restate/sdk/go.proto) which must be imported in your own file, and a directory containing it must be provided to protoc eg with `-I $GOPATH/src/github.com/restatedev/sdk-go/proto` You may additionally provide the option `dev.restate.sdk.go.handler_type` to denote that the handler is a shared handler. ```proto service.proto {"CODE_LOAD::go/develop/codegen/proto/service.proto#virtual_object"} theme={null} import "dev/restate/sdk/go.proto"; service Counter { option (dev.restate.sdk.go.service_type) = VIRTUAL_OBJECT; rpc Add (AddRequest) returns (AddResponse) {} rpc Get (GetRequest) returns (GetResponse) { option (dev.restate.sdk.go.handler_type) = SHARED; } } service Workflow { option (dev.restate.sdk.go.service_type) = WORKFLOW; rpc Run (RunRequest) returns (RunResponse) {} rpc GetStatus (GetStatusRequest) returns (GetStatusResponse) {} } ``` Clients are instantiated with a key: ```go {"CODE_LOAD::go/develop/codegen/codegen.go#virtual_object_client"} theme={null} client := proto.NewCounterClient(ctx, "key-1") resp, err := client.Add().Request(&proto.AddRequest{Delta: 1}) if err != nil { return err } ``` Instead of manually wrangling `protoc` to handle generation and proto imports, it can be a lot easier to use [Buf](https://buf.build/). Buf determines what code to generate based on a `buf.gen.yaml` file: ```yaml theme={null} version: v2 managed: enabled: true plugins: - remote: buf.build/protocolbuffers/go:v1.34.2 out: . opt: paths=source_relative - local: protoc-gen-go-restate out: . opt: paths=source_relative inputs: - directory: . ``` And to allow the import of `dev/restate/sdk/go.proto`, you can specify the dependency in your `buf.yaml`: ```yaml theme={null} version: v2 lint: use: - DEFAULT breaking: use: - FILE deps: - buf.build/restatedev/sdk-go ``` Then you can run `buf generate` to generate the code. # Concurrent Tasks Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/concurrent-tasks Execute multiple tasks concurrently and gather results. When building resilient applications, you often need to perform multiple operations in parallel to improve performance and user experience. Restate provides durable concurrency primitives that allow you to run tasks concurrently while maintaining deterministic execution during replays. ## When to use concurrent tasks Use concurrent tasks when you need to: * Call multiple external services simultaneously (e.g., fetching data from different APIs) * Race multiple operations and use the first result (e.g., trying multiple LLM providers) * Implement timeouts by racing an operation against a timer * Perform batch operations where individual tasks can run in parallel ## Key benefits * **Deterministic replay**: Restate logs the order of completion, ensuring consistent behavior during failures * **Fault tolerance**: If your handler fails, tasks that were already completed will be replayed with their results, while pending tasks will be retried ## Parallelizing tasks Start multiple durable operations concurrently by calling them without immediately awaiting: ```go {"CODE_LOAD::go/develop/journalingresults.go#parallel"} theme={null} call1 := restate.RunAsync(ctx, func(ctx restate.RunContext) (UserData, error) { return fetchUserData(123) }) call2 := restate.RunAsync(ctx, func(ctx restate.RunContext) (OrderHistory, error) { return fetchOrderHistory(123) }) call3 := restate.Service[int](ctx, "AnalyticsService", "CalculateMetrics").RequestFuture(123) user, _ := call1.Result() orders, _ := call2.Result() metrics, _ := call3.Response() ``` Check out the guide on [parallelizing work](guides/parallelizing-work). ## Retrieving results Operations such as calls, awakeables, and `restate.After` return futures which can be safely selected over using `restate.Select`. Restate will log the order in which they complete, to make this deterministic on replay. **Don't use Go routines**: Do not try to combine blocking Restate operations using goroutines, channels or select statements. These cannot be used deterministically, and will likely lead to non-determinism errors upon replay. The only place it is safe to use these types is inside of a restate.Run function. ### Select the first successful completion ```go {"CODE_LOAD::go/develop/journalingresults.go#race"} theme={null} sleepFuture := restate.After(ctx, 30*time.Second) callFuture := restate.Service[string](ctx, "MyService", "MyHandler").RequestFuture("hi") selector := restate.Select(ctx, sleepFuture, callFuture) switch selector.Select() { case sleepFuture: if err := sleepFuture.Done(); err != nil { return "", err } return "sleep won", nil case callFuture: result, err := callFuture.Response() if err != nil { return "", err } return fmt.Sprintf("call won with result: %s", result), nil } ``` Select blocks on the next completed operation or returns `nil` if there are none left. ### Wait for all tasks to complete ```go {"CODE_LOAD::go/develop/journalingresults.go#all"} theme={null} callFuture1 := restate.Service[string](ctx, "MyService", "MyHandler").RequestFuture("hi") callFuture2 := restate.Service[string](ctx, "MyService", "MyHandler").RequestFuture("hi again") selector := restate.Select(ctx, callFuture1, callFuture2) // Collect all results var subResults []string for selector.Remaining() { response, err := selector.Select().(restate.ResponseFuture[string]).Response() if err != nil { return "", err } subResults = append(subResults, response) } ``` # Durable Steps Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/durable-steps Persist results of operations. Restate uses an execution log to replay operations after failures and suspensions. Non-deterministic operations (database calls, HTTP requests, UUID generation) must be wrapped to ensure deterministic replay. ## Run Use `Run` to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate store its result in the execution log. ```go {"CODE_LOAD::go/develop/journalingresults.go#side_effect"} theme={null} result, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return doDbRequest() }) if err != nil { return err } ``` Note that inside `Run`, you cannot use the Restate context (e.g., `get`, `sleep`, or nested `Run`). You should only use methods available on the [`RunContext`](https://pkg.go.dev/github.com/restatedev/sdk-go#RunContext) provided to your function. You can return any payload that can be serialized. By default, serialization is done with [`JSONCodec`](https://pkg.go.dev/github.com/restatedev/sdk-go/encoding#PayloadCodec) which uses `encoding/json`. If you don't need to return anything, you can use `restate.Void{}` which serialises to a nil byte slice. Failures in `Run` are treated the same as any other handler error. Restate will retry it unless configured otherwise or unless a [`TerminalError`](/develop/go/error-handling) is thrown. You can customize how `Run` retries via: ```go {"CODE_LOAD::go/develop/journalingresults.go#side_effect_retry"} theme={null} result, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return doDbRequest() }, // After 10 seconds, give up retrying restate.WithMaxRetryDuration(time.Second*10), // On the first retry, wait 100 milliseconds before next attempt restate.WithInitialRetryInterval(time.Millisecond*100), // Grow retry interval with factor 2 restate.WithRetryIntervalFactor(2.0), // Optional: provide a name for the operation to be visible in the // observability tools. restate.WithName("my_db_request"), ) if err != nil { return err } ``` * You can limit retries by time or count * When the policy is exhausted, a `TerminalError` is thrown * See the [Error Handling Guide](/guides/error-handling) and the [Sagas Guide](/guides/sagas) for patterns like compensation If Restate doesn't receive new journal entries from a service for more than one minute (by default), it will automatically abort the invocation and retry it. However, some business logic can take longer to complete—for example, an LLM call that takes up to 3 minutes to respond. In such cases, you can adjust the service’s [abort timeout and inactivity timeout](/services/configuration) settings to accommodate longer execution times. For more information, see the [error handling guide](/guides/error-handling). ## Deterministic randoms The SDK provides deterministic helpers for random values — seeded by the invocation ID — so they return the **same result on retries**. ### UUIDs To generate stable UUIDs for things like idempotency keys: ```go {"CODE_LOAD::go/develop/journalingresults.go#uuid"} theme={null} uuid := restate.Rand(ctx).UUID() ``` Do not use this in cryptographic contexts. ### Random numbers Methods exist on `restate.Rand(ctx)` for generating `float64` and `uint64`, or otherwise `restate.Rand(ctx).Source()` can be provided to `math/rand/v2` as a source for any random operation: ```go {"CODE_LOAD::go/develop/journalingresults.go#random_nb"} theme={null} randomInt := restate.Rand(ctx).Uint64() randomFloat := restate.Rand(ctx).Float64() randomSource := rand.New(restate.Rand(ctx).Source()) ``` # Scheduling & Timers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/durable-timers Durable timers, scheduled actions, and sleep, backed by Restate. Restate provides durable, fault-tolerant timers that allow you to: * [Sleep](#durable-sleep): Pause a handler for a given duration * [Send delayed messages](#scheduling-async-tasks): Schedule handler invocations in the future * [Set timeouts](#timers-and-timeouts) for async operations * Implement patterns like [cron jobs](#cron-jobs) ## How it works Restate tracks and manages timers, ensuring they survive failures and restarts. For example, if a service is sleeping for 12 hours and fails after 8 hours, then Restate will make sure it only sleeps 4 hours more. If your handler runs on function-as-a-service platforms like AWS Lambda, Restate suspends the handler while it is sleeping, to free up resources. Since these platforms charge on execution time, this saves costs. ## Durable sleep To pause a handler for a set duration: ```go {"CODE_LOAD::go/develop/durabletimers.go#here"} theme={null} if err := restate.Sleep(ctx, 10*time.Second); err != nil { return "", err } ``` There is no hard limit on how long you can sleep. You can sleep for months or even years, but keep in mind: * If you sleep in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. * You need to keep the deployment version until the invocation completes (see [versioning](/services/versioning)). So for long sleeps, we recommend breaking this up into multiple handlers that call each other with [delayed messages](/develop/go/service-communication#delayed-messages). The Restate SDK calculates the wake-up time based on the delay you specify. The Restate Server then uses this calculated time to wake up the handler. If the Restate Server and the SDK have different system clocks, the sleep duration might not be accurate. So make sure that the system clock of the Restate Server and the SDK have the same timezone and are synchronized. A mismatch can cause timers to fire earlier or later than expected. ## Scheduling async tasks To invoke a handler at a later time, use [delayed messages](/develop/go/service-communication#delayed-messages). In theory, you can schedule future work in Restate in two ways: 1. **Delayed messages** (recommended) 2. **Sleep + send** - sleeping in the current handler, then sending a message At first sight, both approaches might seem to achieve similar results. However, we recommend to use delayed messages for the following reasons: * **Handler completes immediately**: The calling handler can finish execution and complete the invocation without waiting for the delay to finish * **No Virtual Object blocking**: Other requests to the same Virtual Object can be processed during the delay period * **Better for service versioning**: No long-running invocations that require keeping old service deployments around for a long time (see [service versioning](/services/versioning)) ## Timers and timeouts Most context actions are async actions that return a future. You can use [Restate's combinators](/develop/go/concurrent-tasks) to race an async operation against a timeout: ```go {"CODE_LOAD::go/develop/durabletimers.go#timer"} theme={null} sleepFuture := restate.After(ctx, 30*time.Second) callFuture := restate.Service[string](ctx, "MyService", "MyHandler").RequestFuture("hi") selector := restate.Select(ctx, sleepFuture, callFuture) switch selector.Select() { case sleepFuture: if err := sleepFuture.Done(); err != nil { return "", err } return "sleep won", nil case callFuture: result, err := callFuture.Response() if err != nil { return "", err } return fmt.Sprintf("call won with result: %s", result), nil } ``` ## Cron jobs Restate does not yet include native cron support, but you can implement your own cron scheduler using: * Durable timers * Virtual Objects * A repeat loop or sleep-schedule pattern Check out the guide on [implementing cron jobs](/guides/cron). # Error Handling Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/error-handling Stop infinite retries with Terminal Errors. Restate handles retries for failed invocations. Check out the [Error Handling guide](/guides/error-handling) to learn more about how Restate handles transient errors, terminal errors, retries, and timeouts. ## Retry strategies By default, Restate does infinite retries with an exponential backoff strategy. Check out the [error handling guide](/guides/error-handling) to learn how to customize this. ## Terminal Errors For failures for which you do not want retries, but instead want the invocation to end and the error message to be propagated back to the caller, you can throw a **terminal error**. You can throw a `TerminalError` with an optional HTTP status code and a message anywhere in your handler, as follows: ```go {"CODE_LOAD::go/develop/errorhandling.go#here"} theme={null} return restate.TerminalError(fmt.Errorf("Something went wrong."), 500) ``` You can catch terminal errors, and build your control flow around it. When you throw a terminal error, you might need to undo the actions you did earlier in your handler to make sure that your system remains in a consistent state. Have a look at our [sagas guide](/guides/sagas) to learn more. # External Events Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/external-events Handle external events and human-in-the-loop patterns with durable waiting primitives. Sometimes your handlers need to pause and wait for external processes to complete. This is common in: * **Human-in-the-loop workflows** (approvals, reviews, manual steps) * **External system integration** (waiting for webhooks, async APIs) * **AI agent patterns** (tool execution, human oversight) This pattern is also known as the **callback** or **task token** pattern. ## Two Approaches Restate provides two primitives for handling external events: | Primitive | Use Case | Key Feature | | -------------------- | -------------------------- | ------------------------------------ | | **Awakeables** | Services & Virtual Objects | Unique ID-based completion | | **Durable Promises** | Workflows only | Named promises for simpler signaling | ## How it works Implementing this pattern in a distributed system is tricky, since you need to ensure that the handler can recover from failures and resume waiting for the external event. Restate makes promises are durable and distributed. They survive crashes and can be resolved or rejected by any handler in the workflow. To save costs on FaaS deployments, Restate lets the handler [suspend](/foundations/key-concepts#suspensions-on-faas) while awaiting the promise, and invokes it again when the result is available. ## Awakeables **Best for:** Services and Virtual Objects where you need to coordinate with external systems. ### Creating and waiting for awakeables 1. **Create an awakeable** - Get a unique ID and promise 2. **Send the ID externally** - Pass the awakeable ID to your external system 3. **Wait for result** - Your handler [suspends](/foundations/key-concepts#suspensions-on-faas) until the external system responds ```go {"CODE_LOAD::go/develop/awakeable.go#here"} theme={null} awakeable := restate.Awakeable[string](ctx) awakeableId := awakeable.Id() if _, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return requestHumanReview(awakeableId) }); err != nil { return err } review, err := awakeable.Result() if err != nil { return err } ``` You can resolve an awakeable with any payload that can be serialized. By default, serialization is done with [`JSONCodec`](https://pkg.go.dev/github.com/restatedev/sdk-go/encoding#PayloadCodec) which uses `encoding/json`. If you don't need to provide anything, you can use `restate.Void{}` which serializes to a nil byte slice. Note that if you wait for an awakeable in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. ### Resolving/rejecting Awakeables External processes complete awakeables in two ways: * **Resolve** with success data → handler continues normally * **Reject** with error reason → throws a [terminal error](/develop/go/error-handling) in the waiting handler #### Via SDK (from other handlers) **Resolve:** ```go {"CODE_LOAD::go/develop/awakeable.go#resolve"} theme={null} restate.ResolveAwakeable(ctx, awakeableId, "Looks good!") ``` **Reject:** ```go {"CODE_LOAD::go/develop/awakeable.go#reject"} theme={null} restate.RejectAwakeable(ctx, awakeableId, fmt.Errorf("Cannot do review")) ``` #### Via HTTP API External systems can complete awakeables using Restate's HTTP API: **Resolve with data:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/resolve \ --json '"Looks good!"' ``` **Reject with error:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/reject \ -H 'content-type: text/plain' \ -d 'Review rejected: insufficient documentation' ``` ## Durable Promises **Best for:** Workflows where you need to signal between different workflow handlers. **Key differences from awakeables:** * No ID management - use logical names instead * Scoped to workflow execution lifetime Use this for: * Sending data to the run handler * Have handlers wait for events emitted by the run handler After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). The results of resolved Durable Promises remain available during this time. Update the retention time via the [service configuration](/services/configuration). ### Creating and waiting for promises Wait for a promise by name: ```go {"CODE_LOAD::go/develop/durablepromise.go#promise"} theme={null} review, err := restate.Promise[string](ctx, "review").Result() ``` ### Resolving/rejecting promises Resolve/reject from any workflow handler: ```go {"CODE_LOAD::go/develop/durablepromise.go#resolve_promise"} theme={null} err := restate.Promise[string](ctx, "review").Resolve(review) if err != nil { return err } ``` ### Complete workflow example ```go expandable {"CODE_LOAD::go/develop/durablepromise.go#review"} theme={null} type ReviewWorkflow struct{} func (ReviewWorkflow) Run(ctx restate.WorkflowContext, documentId string) (string, error) { // Send document for review if _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return restate.Void{}, askReview(documentId) }); err != nil { return "", err } // Wait for external review submission review, err := restate.Promise[string](ctx, "review").Result() if err != nil { return "", err } // Process the review result return processReview(documentId, review) } func (ReviewWorkflow) SubmitReview(ctx restate.WorkflowSharedContext, review string) error { // Signal the waiting run handler return restate.Promise[string](ctx, "review").Resolve(review) } ``` ### Two signaling patterns **External → Workflow** (shown above): External handlers signal the run handler * Use for human approvals, external API responses, manual interventions * External handlers call the handler which resolves the promise **Workflow → External**: Run handler signals other handlers waiting for workflow events * Use for step completion notifications, status updates, result broadcasting * Run handler resolves promises that external handlers are awaiting ## Best Practices * **Use awakeables** for services/objects coordinating with external systems * **Use durable promises** for workflow signaling * **Always handle rejections** to gracefully manage failures * **Include timeouts** for long-running external processes # Logging Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/logging Configure the log level of your services. The Go SDK uses the standard library [`log/slog`](https://pkg.go.dev/log/slog) package for logging. By default logs will go to the slog default handler. However, you may provide any slog handler upon creating the Restate server instance, which allows you to direct the logs to other log libraries: ```go {"CODE_LOAD::go/develop/logging.go#handler"} theme={null} myHandler := slog.NewJSONHandler(os.Stdout, nil) server.NewRestate(). WithLogger(myHandler, true). Bind(restate.Reflect(Monitoring{})). Start(context.Background(), "0.0.0.0:9080") ``` **Avoiding duplicate logging** If you use the default slog logger or another logger, log statements will be printed over and over again during replays. To avoid this, you can use the Restate context logger, which is a `*slog.Logger` that suppresses duplicate log statements during replays: ```go {"CODE_LOAD::go/develop/logging.go#logger"} theme={null} ctx.Log().Info("This will not be printed again during replays") ctx.Log().Debug("This will not be printed again during replays") ``` When providing a custom log handler using [`WithLogger`](https://pkg.go.dev/github.com/restatedev/sdk-go/server#Restate.WithLogger), you can provide `false` as the second argument, in which case logs will not be dropped during replay allowing you to handle them as you prefer. You can still determine whether they would have been dropped using [`rcontext.LogContextFrom`](https://pkg.go.dev/github.com/restatedev/sdk-go/rcontext#LogContextFrom) on the context passed to your log handler. # Service Communication Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/service-communication Call other services from your handler. Your Restate handler can call other handlers in three ways: * **[Request-response calls](#request-response-calls)**: Call and wait for a response * **[One-way messages](#sending-messages)**: Send a message and continue * **[Delayed messages](#delayed-messages)**: Send a message after a delay To call a service from an external application, see the [HTTP](/services/invocation/http), [Kafka](/services/invocation/kafka), or [SDK Clients](/services/invocation/clients/typescript-sdk) documentation. [Learn how Restate how it works](/foundations/key-concepts#resilient-communication) ## Request-response calls To call a Restate handler and wait for its result: ```go {"CODE_LOAD::go/develop/servicecommunication.go#request_response"} theme={null} // To call a Service: svcResponse, err := restate.Service[string](ctx, "MyService", "MyHandler"). Request("Hi") if err != nil { return err } // To call a Virtual Object: objResponse, err := restate.Object[string](ctx, "MyObject", "Mary", "MyHandler"). Request("Hi") if err != nil { return err } // To call a Workflow: // `run` handler — can only be called once per workflow ID wfResponse, err := restate.Workflow[bool](ctx, "MyWorkflow", "my-workflow-id", "Run"). Request("Hi") if err != nil { return err } // Other handlers can be called anytime within workflow retention status, err := restate.Workflow[restate.Void](ctx, "MyWorkflow", "my-workflow-id", "GetStatus"). Request("Hi again") if err != nil { return err } ``` After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). Update this via the [service configuration](/services/configuration). Request-response calls between [exclusive handlers](/foundations/handlers#handler-behavior) of Virtual Objects may lead to deadlocks: * Cross deadlock: A → B and B → A (same keys). * Cycle deadlock: A → B → C → A. Use the UI or CLI to [cancel](/services/invocation/managing-invocations#cancel) and unblock deadlocked invocations. ## Sending messages To send a message to another Restate handler without waiting for a response: ```go {"CODE_LOAD::go/develop/servicecommunication.go#one_way"} theme={null} // To message a Service: restate.ServiceSend(ctx, "MyService", "MyHandler").Send("Hi") // To message a Virtual Object: restate.ObjectSend(ctx, "MyObject", "Mary", "MyHandler").Send("Hi") // To message a Workflow: // `run` handler — can only be called once per workflow ID restate.WorkflowSend(ctx, "MyWorkflow", "my-workflow-id", "Run"). Send("Hi") // Other handlers can be called anytime within workflow retention restate.WorkflowSend(ctx, "MyWorkflow", "my-workflow-id", "InteractWithWorkflow"). Send("Hi again") ``` Restate handles message delivery and retries, so the handler can complete and return without waiting for the message to be processed. Calls to a Virtual Object execute in order of arrival, serially. Example: ```go {"CODE_LOAD::go/develop/servicecommunication.go#ordering"} theme={null} restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("I'm call A") restate.ObjectSend(ctx, "MyService", "Mary", "MyHandler").Send("I'm call B") ``` Call A is guaranteed to execute before B. However, other invocations may interleave between A and B. ## Delayed messages To send a message after a delay: ```go {"CODE_LOAD::go/develop/servicecommunication.go#delayed"} theme={null} // To message a Service with a delay: restate.ServiceSend(ctx, "MyService", "MyHandler"). Send("Hi", restate.WithDelay(5*time.Hour)) // To message a Virtual Object with a delay: restate.ObjectSend(ctx, "MyObject", "Mary", "MyHandler"). Send("Hi", restate.WithDelay(5*time.Hour)) // To message a Workflow with a delay: restate.WorkflowSend(ctx, "MyWorkflow", "my-workflow-id", "Run"). Send("Hi", restate.WithDelay(5*time.Hour)) ``` Learn [how this is different](/develop/ts/durable-timers#scheduling-async-tasks) from sleeping and then sending a message. ## Using an idempotency key To prevent duplicate executions of the same call, add an idempotency key: ```go {"CODE_LOAD::go/develop/servicecommunication.go#idempotency_key"} theme={null} restate. ServiceSend(ctx, "MyService", "MyHandler"). // Send attaching idempotency key Send("Hi", restate.WithIdempotencyKey("my-idempotency-key")) ``` Restate automatically deduplicates calls made during the same handler execution, so there's no need to provide an idempotency key in that case. However, if multiple handlers might call the same service independently, you can use an idempotency key to ensure deduplication across those calls. ## Attach to an invocation To wait for or get the result of a previously sent message: ```go {"CODE_LOAD::go/develop/servicecommunication.go#attach"} theme={null} // Execute the request and retrieve the invocation id invocationId := restate. ServiceSend(ctx, "MyService", "MyHandler"). // Optional: send attaching idempotency key Send("Hi", restate.WithIdempotencyKey("my-idempotency-key")). GetInvocationId() // Later re-attach to the request response, err := restate.AttachInvocation[string](ctx, invocationId).Response() ``` * With an idempotency key: Wait for completion and retrieve the result. * Without an idempotency key: Can only wait, not retrieve the result. ## Cancel an invocation To cancel a running handler: ```go {"CODE_LOAD::go/develop/servicecommunication.go#cancel"} theme={null} // Execute the request and retrieve the invocation id invocationId := restate. ServiceSend(ctx, "MyService", "MyHandler"). Send("Hi"). GetInvocationId() // I don't need this invocation anymore, let me just cancel it restate.CancelInvocation(ctx, invocationId) ``` ## See also * **[SDK Clients](/develop/go/service-communication)**: Call Restate services from external applications * **[Error Handling](/develop/go/error-handling)**: Handle failures and terminal errors in service calls * **[Durable Timers](/develop/go/durable-timers)**: Implement timeouts for your service calls * **[Sagas](/guides/sagas)**: Roll back or compensate for canceled service calls. # Services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/services Implementing Restate services with the Go SDK. The Restate Go SDK is open source and available on [GitHub](https://github.com/restatedev/sdk-go). The Restate SDK lets you implement **handlers**. Handlers can be part of a **[Basic Service](/foundations/services#basic-service)**, a **[Virtual Object](/foundations/services#virtual-object)**, or a **[Workflow](/foundations/services#workflow)**. This page shows how to define them with the Go SDK. ## Prerequisites * Go: >= 1.21.0 ## Getting started Get started quickly with the [Go Quickstart](/quickstart#go). Add the `github.com/restatedev/sdk-go` dependency to your project to start developing Restate services. ## Basic Services [Basic Services](/foundations/services) group related **handlers** and expose them as callable endpoints: ```go {"CODE_LOAD::go/develop/myservice/main.go?collapse_prequel"} theme={null} type MyService struct{} func (MyService) MyHandler(ctx restate.Context, greeting string) (string, error) { return fmt.Sprintf("%s!", greeting), nil } func main() { if err := server.NewRestate(). Bind(restate.Reflect(MyService{})). Start(context.Background(), "0.0.0.0:9080"); err != nil { log.Fatal(err) } } ``` * Define a Service by implementing exported handlers on any struct. * Handlers have the `Context` as the first argument. Within the handler, you use the `Context` to interact with Restate. The SDK stores the actions you do on the context in the Restate journal to make them durable. * The handler input parameters and return type can be of any type, as long as they can be serialized. By default, serialization is done with [`JSONCodec`](https://pkg.go.dev/github.com/restatedev/sdk-go/encoding#PayloadCodec) which uses `encoding/json`. Input types, output types, and even errors are not mandatory and can be omitted if your handler doesn't need them. * The service will be reachable under the struct name `MyService`, so the service can be called at `/MyService/MyHandler`. * Pass the `MyService` struct to `restate.Reflect` which uses reflection to turn the methods into handlers. It will skip unexported methods and those that don't have the expected signature - see the [package documentation](https://pkg.go.dev/github.com/restatedev/sdk-go#Reflect) for a list of allowed signatures. * Finally, create a server listening on the specified address and bind the service(s) to it. The Go SDK also supports defining handlers and input/output types using code generation from [Protocol Buffers](https://protobuf.dev/). See [Code Generation](/develop/go/code-generation) for more details. ## Virtual Objects [Virtual Objects](/foundations/services) are services that are stateful and key-addressable — each object instance has a unique ID and persistent state. ```go {"CODE_LOAD::go/develop/myvirtualobject/main.go?collapse_prequel"} theme={null} type MyObject struct{} func (MyObject) MyHandler(ctx restate.ObjectContext, greeting string) (string, error) { return fmt.Sprintf("%s %s!", greeting, restate.Key(ctx)), nil } func (MyObject) MyConcurrentHandler(ctx restate.ObjectSharedContext, greeting string) (string, error) { return fmt.Sprintf("%s %s!", greeting, restate.Key(ctx)), nil } func main() { if err := server.NewRestate(). Bind(restate.Reflect(MyObject{})). Start(context.Background(), "0.0.0.0:9080"); err != nil { log.Fatal(err) } } ``` * Define a Virtual Object by implementing exported handlers on any struct. * You can retrieve the key of the object you are in via `restate.Key(ctx)`. * Virtual Objects can have [exclusive and shared handlers](/foundations/handlers#handler-behavior). - Exclusive handlers receive an `ObjectContext`, allowing read/write access to object state. - Shared handlers are wrapped in `handlers.object.shared(...)` and use the `ObjectSharedContext` ## Workflows [Workflows](/foundations/services) are long-lived processes with a defined lifecycle. They run once per key and are ideal for orchestrating multi-step operations, which require external interaction via signals and queries. ```go {"CODE_LOAD::go/develop/myworkflow/main.go?collapse_prequel"} theme={null} type MyWorkflow struct{} func (MyWorkflow) Run(ctx restate.WorkflowContext, req string) (string, error) { // implement the workflow logic here return "success", nil } func (MyWorkflow) InteractWithWorkflow(ctx restate.WorkflowSharedContext) error { // implement interaction logic here // e.g. resolve a promise that the workflow is waiting on return nil } func main() { server := server.NewRestate(). Bind(restate.Reflect(MyWorkflow{})) if err := server.Start(context.Background(), ":9080"); err != nil { slog.Error("application exited unexpectedly", "err", err.Error()) os.Exit(1) } } ``` * Define a Workflow by implementing exported handlers on any struct. - Every workflow **must** include a `Run` handler: * This is the main orchestration entry point * It runs exactly once per workflow execution and uses the `WorkflowContext` * Resubmission of the same workflow will fail with "Previously accepted". The invocation ID can be found in the request header `x-restate-id`. * Use `restate.Key(ctx)` to access the workflow's unique ID - Additional handlers must use the `WorkflowSharedContext` and can signal or query the workflow. They can run concurrently with the `Run` handler and until the retention time expires. ## Configuring services Check out the [service configuration docs](/services/configuration) to learn how to configure service behavior, including timeouts and retention policies. # Serving Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/serving Create an endpoint to serve your services. The Restate SDK is served as a HTTP handler. You can choose to run this in a standalone HTTP2 server, or connect the handler into a FaaS system like Lambda. ## Creating a HTTP2 server 1. Create the Restate Server 2. Bind one or multiple services to it. 3. Listen on the specified port for connections and requests. ```go {"CODE_LOAD::go/develop/serving.go#endpoint"} theme={null} if err := server.NewRestate(). Bind(restate.Reflect(MyService{})). Bind(restate.Reflect(MyObject{})). Bind(restate.Reflect(MyWorkflow{})). Start(context.Background(), ":9080"); err != nil { log.Fatal(err) } ``` If you need to customize the HTTP2 server, or serve over HTTP1.1 you can call `.Handler()` instead of `Start()`, and then use the handler as normal. To discover services over HTTP1.1 you must provide the `--use-http1.1` CLI flag. ```go {"CODE_LOAD::go/develop/serving.go#custom_endpoint"} theme={null} handler, err := server.NewRestate(). Bind(restate.Reflect(MyService{})). Bind(restate.Reflect(MyObject{})). Bind(restate.Reflect(MyWorkflow{})). Handler() if err != nil { log.Fatal(err) } ``` By default, this handler will advertise itself as working bidirectionally; the SDK will try to get completions from the runtime during execution. However, you can use the method `.Bidirectional(false)` on the endpoint builder to change this on platforms that do not support bidirectional communication, such as Lambda. If you don't do this your handler may get stuck. ## Creating a Lambda handler To register your service as a Lambda function change the endpoint into a Lambda handler with `.LambdaHandler()`, and pass this handler to [`lambda.Start`](https://pkg.go.dev/github.com/aws/aws-lambda-go/lambda#Start). ```go {"CODE_LOAD::go/develop/servinglambda.go#lambda"} theme={null} handler, err := server.NewRestate(). Bind(restate.Reflect(MyService{})). Bind(restate.Reflect(MyObject{})). Bind(restate.Reflect(MyWorkflow{})). Bidirectional(false). LambdaHandler() if err != nil { log.Fatal(err) } lambda.Start(handler) ``` Have a look at the [deployment section](/services/deploy/lambda) for guidance on how to deploy your services on AWS Lambda. The implementation of your services and handlers remains the same for both deployment options. ## Validating request identity SDKs can validate that incoming requests come from a particular Restate instance. You can find out more about request identity in the [Security docs](/services/security#locking-down-service-access) ```go {"CODE_LOAD::go/develop/serving.go#identity"} theme={null} if err := server.NewRestate(). Bind(restate.Reflect(MyService{})). WithIdentityV1("publickeyv1_w7YHemBctH5Ck2nQRQ47iBBqhNHy4FV7t2Usbye2A6f"). Start(context.Background(), ":9080"); err != nil { log.Fatal(err) } ``` # State Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/go/state Store key-value state in Restate. Restate lets you persist key-value (K/V) state using its embedded K/V store. ## Key characteristics [Learn about Restate's embedded K/V store](/foundations/key-concepts#consistent-state). **State is only available for Virtual Objects and Workflows.** Scope & retention: * For Virtual Objects: State is scoped per object key and retained indefinitely. It is persisted and shared across all invocations for that object until explicitly cleared. * For Workflows: State is scoped per workflow execution (workflow ID) and retained only for the duration of the workflow’s configured retention time. Access Rules: * [Exclusive handlers](/foundations/handlers#handler-behavior) (e.g., `run()` in workflows) can read and write state. * [Shared handlers](/foundations/handlers#handler-behavior) can only read state and cannot mutate it. You can inspect and edit the K/V state via the UI and the [CLI](/services/introspection#inspecting-application-state). ## List all state keys To retrieve all keys for which the current Virtual Object has stored state: ```go {"CODE_LOAD::go/develop/state.go#statekeys"} theme={null} stateKeys, err := restate.Keys(ctx) if err != nil { return err } ``` ## Get state value To read a value by key: ```go {"CODE_LOAD::go/develop/state.go#get"} theme={null} myString := "my-default" if s, err := restate.Get[*string](ctx, "my-string-key"); err != nil { return err } else if s != nil { myString = *s } myNumber, err := restate.Get[int](ctx, "my-number-key") if err != nil { return err } ``` Returns null if the key doesn't exist. ## Set state value To write or update a value: ```go {"CODE_LOAD::go/develop/state.go#set"} theme={null} restate.Set(ctx, "my-key", "my-new-value") ``` ## Clear state key To delete a specific key: ```go {"CODE_LOAD::go/develop/state.go#clear"} theme={null} restate.Clear(ctx, "my-key") ``` ## Clear all state keys To remove all stored state for the current Virtual Object: ```go {"CODE_LOAD::go/develop/state.go#clear_all"} theme={null} restate.ClearAll(ctx) ``` ## Advanced: Eager vs. lazy state loading Restate supports two modes for loading state in handlers: ### Eager state (default) * **How it works**: State is automatically sent with the request when invoking a handler * **Benefits**: State is available immediately when the handler starts executing * **Behavior**: All reads and writes to state are local to the handler execution * **Best for**: Small to medium state objects that are frequently accessed ### Lazy state * **How it works**: State is fetched on-demand on `get` calls from the Restate Server * **Benefits**: Reduces initial request size and memory usage * **Setup**: Enable lazy state in the [service or handler configuration](/services/configuration) * **Best for**: Large state objects that aren't needed in every handler execution # Concurrent Tasks Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/concurrent-tasks Execute multiple tasks concurrently and gather results. When building resilient applications, you often need to perform multiple operations in parallel to improve performance and user experience. Restate provides durable concurrency primitives that allow you to run tasks concurrently while maintaining deterministic execution during replays. ## When to use concurrent tasks Use concurrent tasks when you need to: * Call multiple external services simultaneously (e.g., fetching data from different APIs) * Race multiple operations and use the first result (e.g., trying multiple LLM providers) * Implement timeouts by racing an operation against a timer * Perform batch operations where individual tasks can run in parallel ## Key benefits * **Deterministic replay**: Restate logs the order of completion, ensuring consistent behavior during failures * **Fault tolerance**: If your handler fails, tasks that were already completed will be replayed with their results, while pending tasks will be retried ## Parallelizing tasks Start multiple durable operations concurrently by calling them without immediately awaiting: ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#parallel"} theme={null} // Start operations concurrently using DurableFuture var call1 = ctx.runAsync(UserData.class, () -> fetchUserData(123)); var call2 = ctx.runAsync(OrderHistory.class, () -> fetchOrderHistory(123)); var call3 = AnalyticsServiceClient.fromContext(ctx).calculateMetric(123); // Now wait for results as needed UserData user = call1.await(); OrderHistory orders = call2.await(); Integer metric = call3.await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#parallel"} theme={null} val call1 = ctx.runAsync { fetchUserData(123) } val call2 = ctx.runAsync { fetchOrderHistory(123) } val call3 = AnalyticsServiceClient.fromContext(ctx).calculateMetric(123) // Now wait for results as needed val user: UserData = call1.await() val orders: OrderHistory = call2.await() val metric: Int = call3.await() ``` Check out the guide on [parallelizing work](guides/parallelizing-work). ## Retrieving results Restate provides several patterns for coordinating concurrent tasks. All patterns use `DurableFuture` combinators that log the order of completion, ensuring deterministic behavior during replays. ### Wait for the first completion **Select** creates a `DurableFuture` that returns on the first completed `DurableFuture` of the provided ones. The semantics are similar to [`CompleteableFuture.anyOf()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletableFuture.html#anyOf\(java.util.concurrent.CompletableFuture...\)), but the outcome is stored in the Restate journal to be deterministically replayable. ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#combine_any"} theme={null} boolean res = Select.select().or(a1).or(a2).or(a3).await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#combine_any"} theme={null} val resSelect = select { a1.onAwait { it } a2.onAwait { it } a3.onAwait { it } } .await() ``` ### Wait for all tasks to complete **Await all** creates a `DurableFuture` that awaits for all the provided DurableFutures to resolve. The semantics are similar to [`CompleteableFuture.allOf()`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletableFuture.html#allOf\(java.util.concurrent.CompletableFuture...\)), but the outcome is stored in the Restate journal to be deterministically replayable. ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#combine_all"} theme={null} DurableFuture.all(a1, a2, a3).await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#combine_all"} theme={null} listOf(a1, a2, a3).awaitAll() ``` # Durable Steps Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/durable-steps Persist results of operations. Restate uses an execution log to replay operations after failures and suspensions. Non-deterministic operations (database calls, HTTP requests, UUID generation) must be wrapped to ensure deterministic replay. ## Run Use `ctx.run` to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate store its result in the execution log. ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#side_effect"} theme={null} String output = ctx.run(String.class, () -> doDbRequest()); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#side_effect"} theme={null} val output = ctx.runBlock { doDbRequest() } ``` Note that inside `ctx.run`, you cannot use the Restate context (e.g., `ctx.get`, `ctx.sleep`, or nested `ctx.run`). To run an action asynchronously, use `ctx.runAsync`: ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#async_side_effect"} theme={null} DurableFuture myRunFuture = ctx.runAsync(String.class, () -> doSomethingSlow()); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#async_side_effect"} theme={null} val myRunFuture = ctx.runAsync { doSomethingSlow() } ``` You can use this to combine your run actions with other asynchronous operations, like waiting for a timer or an awakeable. Check out [Concurrent tasks](/develop/java/concurrent-tasks). Have a look at the [serialization docs](/develop/java/serialization) to learn how to serialize and deserialize your objects. Failures in `ctx.run` are treated the same as any other handler error. Restate will retry it unless configured otherwise or unless a [`TerminalException`](/develop/java/error-handling) is thrown. You can customize how `ctx.run` retries via: ```java Java {"CODE_LOAD::java/src/main/java/develop/RetryRunService.java#here"} theme={null} try { RetryPolicy myRunRetryPolicy = RetryPolicy.defaultPolicy() .setInitialDelay(Duration.ofMillis(500)) .setExponentiationFactor(2) .setMaxDelay(Duration.ofSeconds(10)) .setMaxAttempts(10) .setMaxDuration(Duration.ofMinutes(5)); ctx.run("my-run", myRunRetryPolicy, () -> writeToOtherSystem()); } catch (TerminalException e) { // Handle the terminal error after retries exhausted // For example, undo previous actions (see sagas guide) and // propagate the error back to the caller } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/RetryRunService.kt#here"} theme={null} try { val myRunRetryPolicy = RetryPolicy( initialDelay = 5.seconds, exponentiationFactor = 2.0f, maxDelay = 60.seconds, maxAttempts = 10, maxDuration = 5.minutes) ctx.runBlock("write", myRunRetryPolicy) { writeToOtherSystem() } } catch (e: TerminalException) { // Handle the terminal error after retries exhausted // For example, undo previous actions (see sagas guide) and // propagate the error back to the caller throw e } ``` * You can limit retries by time or count * When the policy is exhausted, a `TerminalException` is thrown * See the [Error Handling Guide](/guides/error-handling) and the [Sagas Guide](/guides/sagas) for patterns like compensation If Restate doesn't receive new journal entries from a service for more than one minute (by default), it will automatically abort the invocation and retry it. However, some business logic can take longer to complete—for example, an LLM call that takes up to 3 minutes to respond. In such cases, you can adjust the service’s [abort timeout and inactivity timeout](/services/configuration) accommodate longer execution times. For more information, see the [error handling guide](/guides/error-handling). ## Deterministic randoms The SDK provides deterministic helpers for random values — seeded by the invocation ID — so they return the **same result on retries**. ### UUIDs To generate stable UUIDs for things like idempotency keys: ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#uuid"} theme={null} UUID uuid = ctx.random().nextUUID(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#uuid"} theme={null} val uuid = ctx.random().nextUUID() ``` Do not use this in cryptographic contexts. ### Random numbers To generate a deterministic float between `0` and `1`: ```java Java {"CODE_LOAD::java/src/main/java/develop/JournalingResults.java#random_nb"} theme={null} int value = ctx.random().nextInt(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/JournalingResults.kt#random_nb"} theme={null} val value = ctx.random().nextInt() ``` This behaves like the standard library `Random` class but is deterministically replayable. You can use any of the methods of [`java.util.Random`](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Random.html) to generate random numbers: for example, `nextBoolean()`, `nextLong()`, `nextFloat()`, etc. # Scheduling & Timers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/durable-timers Durable timers, scheduled actions, and sleep, backed by Restate. Restate provides durable, fault-tolerant timers that allow you to: * [Sleep](#durable-sleep): Pause a handler for a given duration * [Send delayed messages](#scheduling-async-tasks): Schedule handler invocations in the future * [Set timeouts](#timers-and-timeouts) for async operations * Implement patterns like [cron jobs](#cron-jobs) ## How it works Restate tracks and manages timers, ensuring they survive failures and restarts. For example, if a service is sleeping for 12 hours and fails after 8 hours, then Restate will make sure it only sleeps 4 hours more. If your handler runs on function-as-a-service platforms like AWS Lambda, Restate suspends the handler while it is sleeping, to free up resources. Since these platforms charge on execution time, this saves costs. ## Durable sleep To pause a handler for a set duration: ```java Java {"CODE_LOAD::java/src/main/java/develop/DurableTimers.java#sleep"} theme={null} ctx.sleep(Duration.ofSeconds(10)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/DurableTimers.kt#sleep"} theme={null} ctx.sleep(10.seconds) ``` There is no hard limit on how long you can sleep. You can sleep for months or even years, but keep in mind: * If you sleep in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. * You need to keep the deployment version until the invocation completes (see [versioning](/services/versioning)). So for long sleeps, we recommend breaking this up into multiple handlers that call each other with [delayed messages](/develop/java/service-communication#delayed-messages). The Restate SDK calculates the wake-up time based on the delay you specify. The Restate Server then uses this calculated time to wake up the handler. If the Restate Server and the SDK have different system clocks, the sleep duration might not be accurate. So make sure that the system clock of the Restate Server and the SDK have the same timezone and are synchronized. A mismatch can cause timers to fire earlier or later than expected. ## Scheduling async tasks To invoke a handler at a later time, use [delayed messages](/develop/java/service-communication#delayed-messages). In theory, you can schedule future work in Restate in two ways: 1. **Delayed messages** (recommended) 2. **Sleep + send** - sleeping in the current handler, then sending a message At first sight, both approaches might seem to achieve similar results. However, we recommend to use delayed messages for the following reasons: * **Handler completes immediately**: The calling handler can finish execution and complete the invocation without waiting for the delay to finish * **No Virtual Object blocking**: Other requests to the same Virtual Object can be processed during the delay period * **Better for service versioning**: No long-running invocations that require keeping old service deployments around for a long time (see [service versioning](/services/versioning)) ## Timers and timeouts Most context actions are async actions that return a `DurableFuture`. Restate's `DurableFuture` supports setting timeouts, allowing you to bound how long your code waits for an operation. When an operation times out, it throws a Restate `TimeoutException`. ```java Java {"CODE_LOAD::java/src/main/java/develop/DurableTimers.java#timer"} theme={null} try { MyServiceClient.fromContext(ctx).myHandler("Hi!").await(Duration.ofSeconds(5)); } catch (TimeoutException e) { // Handle the timeout error } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/DurableTimers.kt#timer"} theme={null} try { MyServiceClient.fromContext(ctx).myHandler("Hi!").await(5.seconds) } catch (e: TimeoutException) { // Handle the timeout } ``` For alternative ways of racing async operations, have a look at the [Durable Future combinators docs](/develop/java/concurrent-tasks). ## Cron jobs Restate does not yet include native cron support, but you can implement your own cron scheduler using: * Durable timers * Virtual Objects * A repeat loop or sleep-schedule pattern Check out the guide on [implementing cron jobs](/guides/cron). # Error Handling Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/error-handling Stop infinite retries with Terminal Errors. Restate handles retries for failed invocations. Check out the [Error Handling guide](/guides/error-handling) to learn more about how Restate handles transient errors, terminal errors, retries, and timeouts. ## Retry strategies By default, Restate does infinite retries with an exponential backoff strategy. Check out the [error handling guide](/guides/error-handling) to learn how to customize this. ## Terminal Errors For failures for which you do not want retries, but instead want the invocation to end and the error message to be propagated back to the caller, you can throw a **terminal error**. You can throw a `TerminalException` with an optional HTTP status code and a message anywhere in your handler, as follows: ```java Java {"CODE_LOAD::java/src/main/java/develop/ErrorHandling.java#here"} theme={null} throw new TerminalException(500, "Something went wrong"); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ErrorHandling.kt#here"} theme={null} throw TerminalException(500, "Something went wrong") ``` You can catch terminal errors, and build your control flow around it. When throwing a terminal error, undo earlier handler actions to keep the system consistent. See our [sagas guide](/guides/sagas) for details. # External Events Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/external-events Handle external events and human-in-the-loop patterns with durable waiting primitives. Sometimes your handlers need to pause and wait for external processes to complete. This is common in: * **Human-in-the-loop workflows** (approvals, reviews, manual steps) * **External system integration** (waiting for webhooks, async APIs) * **AI agent patterns** (tool execution, human oversight) This pattern is also known as the **callback** or **task token** pattern. ## Two Approaches Restate provides two primitives for handling external events: | Primitive | Use Case | Key Feature | | -------------------- | -------------------------- | ------------------------------------ | | **Awakeables** | Services & Virtual Objects | Unique ID-based completion | | **Durable Promises** | Workflows only | Named promises for simpler signaling | ## How it works Implementing this pattern in a distributed system is tricky, since you need to ensure that the handler can recover from failures and resume waiting for the external event. Restate makes promises are durable and distributed. They survive crashes and can be resolved or rejected by any handler in the workflow. To save costs on FaaS deployments, Restate lets the handler [suspend](/foundations/key-concepts#suspensions-on-faas) while awaiting the promise, and invokes it again when the result is available. ## Awakeables **Best for:** Services and Virtual Objects where you need to coordinate with external systems. ### Creating and waiting for awakeables 1. **Create an awakeable** - Get a unique ID and Future 2. **Send the ID externally** - Pass the awakeable ID to your external system 3. **Wait for result** - Your handler [suspends](/foundations/key-concepts#suspensions-on-faas) until the external system responds ```java Java {"CODE_LOAD::java/src/main/java/develop/Awakeables.java#here"} theme={null} // Create awakeable and get unique ID Awakeable awakeable = ctx.awakeable(String.class); String awakeableId = awakeable.id(); // Send ID to external system (email, queue, webhook, etc.) ctx.run(() -> requestHumanReview(name, awakeableId)); // Handler suspends here until external completion String review = awakeable.await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/Awakeables.kt#here"} theme={null} // Create awakeable and get unique ID val awakeable = ctx.awakeable() val awakeableId = awakeable.id // Send ID to external system (email, queue, webhook, etc.) ctx.runBlock { requestHumanReview(awakeableId) } // Handler suspends here until external completion val review = awakeable.await() ``` To customize serialization, visit the [docs](/develop/java/serialization). Note that if you wait for an awakeable in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. ### Resolving/rejecting Awakeables External processes complete awakeables in two ways: * **Resolve** with success data → handler continues normally * **Reject** with error reason → throws a [terminal error](/develop/java/error-handling) in the waiting handler #### Via SDK (from other handlers) **Resolve:** ```java Java {"CODE_LOAD::java/src/main/java/develop/Awakeables.java#resolve"} theme={null} // Complete with success data ctx.awakeableHandle(awakeableId).resolve(String.class, "Looks good!"); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/Awakeables.kt#resolve"} theme={null} // Complete with success data ctx.awakeableHandle(awakeableId).resolve("Looks good!") ``` **Reject:** ```java Java {"CODE_LOAD::java/src/main/java/develop/Awakeables.java#reject"} theme={null} // Complete with error ctx.awakeableHandle(awakeableId).reject("This cannot be reviewed."); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/Awakeables.kt#reject"} theme={null} // Complete with error ctx.awakeableHandle(awakeableId).reject("This cannot be reviewed.") ``` #### Via HTTP API External systems can complete awakeables using Restate's HTTP API: **Resolve with data:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/resolve \ --json '"Looks good!"' ``` **Reject with error:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/reject \ -H 'content-type: text/plain' \ -d 'Review rejected: insufficient documentation' ``` ## Durable Promises **Best for:** Workflows where you need to signal between different workflow handlers. **Key differences from awakeables:** * No ID management - use logical names instead * Scoped to workflow execution lifetime Use this for: * Sending data to the run handler * Have handlers wait for events emitted by the run handler After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). The results of resolved Durable Promises remain available during this time. Update the retention time via the [service configuration](/services/configuration). ### Creating and waiting for promises Create a promise key: ```java Java {"CODE_LOAD::java/src/main/java/develop/ReviewWorkflow.java#promise_key"} theme={null} private static final DurablePromiseKey REVIEW_PROMISE = DurablePromiseKey.of("review", String.class); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ReviewWorkflow.kt#promise_key"} theme={null} val REVIEW_PROMISE = durablePromiseKey("review") ``` Wait for a promise by name: ```java Java {"CODE_LOAD::java/src/main/java/develop/ReviewWorkflow.java#promise"} theme={null} String review = ctx.promise(REVIEW_PROMISE).future().await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ReviewWorkflow.kt#promise"} theme={null} val review: String = ctx.promise(REVIEW_PROMISE).future().await() ``` Your workflow can [suspend](/foundations/key-concepts#suspensions-on-faas) until the promise is resolved or rejected. ### Resolving/rejecting promises Resolve/reject from any workflow handler: ```java Java {"CODE_LOAD::java/src/main/java/develop/ReviewWorkflow.java#resolve_promise"} theme={null} ctx.promiseHandle(REVIEW_PROMISE).resolve(review); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ReviewWorkflow.kt#resolve_promise"} theme={null} ctx.promiseHandle(REVIEW_PROMISE).resolve(review) ``` ### Complete workflow example ```java Java expandable {"CODE_LOAD::java/src/main/java/develop/ReviewWorkflow.java#here"} theme={null} @Workflow public class ReviewWorkflow { private static final DurablePromiseKey REVIEW_PROMISE = DurablePromiseKey.of("review", String.class); @Workflow public String run(WorkflowContext ctx, String documentId) { // Send document for review ctx.run(() -> askReview(documentId)); // Wait for external review submission String review = ctx.promise(REVIEW_PROMISE).future().await(); // Process the review result return processReview(documentId, review); } @Shared public void submitReview(SharedWorkflowContext ctx, String review) { // Signal the waiting run handler ctx.promiseHandle(REVIEW_PROMISE).resolve(review); } } ``` ```kotlin Kotlin expandable {"CODE_LOAD::kotlin/src/main/kotlin/develop/ReviewWorkflow.kt#here"} theme={null} @Workflow class ReviewWorkflow { companion object { val REVIEW_PROMISE = durablePromiseKey("review") } @Workflow suspend fun run(ctx: WorkflowContext, documentId: String): String { // Send document for review ctx.runBlock { askReview(documentId) } // Wait for external review submission val review: String = ctx.promise(REVIEW_PROMISE).future().await() // Process the review result return processReview(documentId, review) } @Shared suspend fun submitReview(ctx: SharedWorkflowContext, review: String) { // Signal the waiting run handler ctx.promiseHandle(REVIEW_PROMISE).resolve(review) } } ``` ### Two signaling patterns **External → Workflow** (shown above): External handlers signal the run handler * Use for human approvals, external API responses, manual interventions * External handlers call the handler which resolves the promise **Workflow → External**: Run handler signals other handlers waiting for workflow events * Use for step completion notifications, status updates, result broadcasting * Run handler resolves promises that external handlers are awaiting ## Best Practices * **Use awakeables** for services/objects coordinating with external systems * **Use durable promises** for workflow signaling * **Always handle rejections** to gracefully manage failures * **Include timeouts** for long-running external processes # Logging Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/logging Configure the log level of your services. The Java SDK uses [log4j2](https://logging.apache.org/log4j/2.x/manual/configuration.html) as logging facade. To configure the logging, add the file `resources/log4j2.properties`: ```properties theme={null} # Set to debug or trace if log4j initialization is failing status = warn # Console appender configuration appender.console.type = Console appender.console.name = consoleLogger appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %notEmpty{[%X{restateInvocationTarget}]}%notEmpty{[%X{restateInvocationId}]} %c - %m%n # Filter out logging during replay appender.console.filter.replay.type = ContextMapFilter appender.console.filter.replay.onMatch = DENY appender.console.filter.replay.onMismatch = NEUTRAL appender.console.filter.replay.0.type = KeyValuePair appender.console.filter.replay.0.key = restateInvocationStatus appender.console.filter.replay.0.value = REPLAYING # Restate logs to debug level logger.app.name = dev.restate logger.app.level = info logger.app.additivity = false logger.app.appenderRef.console.ref = consoleLogger # Root logger rootLogger.level = info rootLogger.appenderRef.stdout.ref = consoleLogger ``` If you want to do filtering of the logs, you can use `log4j2` filters. Logging on the `INFO` level is enough for most use cases, but you can set the log level of the `dev.restate` classes to `DEBUG` and `TRACE` if you want more info about the internal SDK operations. The SDK injects the following additional metadata to the logging context that can be used for filtering as well: * `restateInvocationTarget`: Invocation Target, e.g. `counter.Counter/Add`. * `restateInvocationId`: [Invocation ID](/services/invocation/http#invocation-identifier). * `restateInvocationStatus`: Invocation status, can be `WAITING_START`, `REPLAYING`, `PROCESSING`, `CLOSED`. # Serialization Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/serialization Customize serialization for SDK actions. Restate sends data over the network for storing state, journaling actions, awakeables, etc. Therefore, Restate needs to serialize and deserialize the journal entries. ## Default serialization The SDK uses by default [Jackson Databind](https://github.com/FasterXML/jackson) for the Java API, and [Kotlinx serialization](https://kotlinlang.org/docs/serialization.html) for the Kotlin API. For example, for state keys: ```java Java {"CODE_LOAD::java/src/main/java/develop/SerializationExample.java#state_keys"} theme={null} // Primitive types var myString = StateKey.of("myString", String.class); // Generic types need TypeRef (similar to Jackson's TypeReference) var myMap = StateKey.of("myMap", TypeTag.of(new TypeRef>() {})); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/SerializationExample.kt#state_keys"} theme={null} // These imports provide extension methods with reified generics import dev.restate.sdk.kotlin.* // Primitive types val myString = stateKey("my-string") // Generic types val myMap = stateKey>("my-map") // Custom generic type val myPerson = stateKey>("my-person") ``` The same applies to journaling actions, awakeables, etc. ## Customizing serialization There are different entrypoints in the SDK to customize (de)serialization, depending on your needs. ### Custom Jackson `ObjectMapper` You can customize the Jackson's [`ObjectMapper`](https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-databind/latest/com/fasterxml/jackson/databind/ObjectMapper.html) by subclassing the [`JacksonSerdeFactory`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/serde/jackson/JacksonSerdeFactory) class: ```java {"CODE_LOAD::java/src/main/java/develop/SerializationExample.java#custom_jackson"} theme={null} class MyJacksonSerdeFactory extends JacksonSerdeFactory { public MyJacksonSerdeFactory() { super(new ObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, true)); } } ``` And annotating your services with [`@CustomSerdeFactory`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/annotation/CustomSerdeFactory): ```java {"CODE_LOAD::java/src/main/java/develop/SerializationExample.java#custom_jackson_service"} theme={null} @CustomSerdeFactory(MyJacksonSerdeFactory.class) @Service class ServiceWithCustomJacksonObjectMapper { @Handler public String greet(Context context) { return "Hello world"; } } ``` ### Custom Kotlinx Serialization `Json` You can customize the Kotlinx Serialization [`Json`](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/) by subclassing the [`KotlinSerializationSerdeFactory`](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin.serialization/-kotlin-serialization-serde-factory/) class: ```kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/CustomSerializationExample.kt#custom"} theme={null} class MyJsonSerdeFactory : KotlinSerializationSerdeFactory(json = Json { prettyPrint = true }) ``` And annotating your services with [`@CustomSerdeFactory`](https://restatedev.github.io/sdk-java/ktdocs/sdk-common/dev.restate.sdk.annotation/-custom-serde-factory/): ```kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/CustomSerializationExample.kt#custom_service"} theme={null} @CustomSerdeFactory(MyJsonSerdeFactory::class) @Service class ServiceWithCustomSerdeFactory { @Handler suspend fun greet(ctx: Context) = "Hello world!" } ``` ### Use custom serializer for a specific operation If you need for a specific operation (e.g. for `Context.run` or `Context.Awakeable`) a custom (de)serializer, you can implement one using the interface [`Serde`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/serde/Serde): ```java {"CODE_LOAD::java/src/main/java/develop/SerializationExample.java#customserdes"} theme={null} class MyPersonSerde implements Serde { @Override public Slice serialize(Person person) { // convert value to a byte array, then wrap in a Slice return Slice.wrap(person.toBytes()); } @Override public Person deserialize(Slice slice) { // convert value to Person return Person.fromBytes(slice.toByteArray()); } } ``` And then use it, for example, in combination with `ctx.run`: ```java {"CODE_LOAD::java/src/main/java/develop/SerializationExample.java#use_person_serde"} theme={null} ctx.run(new MyPersonSerde(), () -> new Person()); ``` ### Use another serialization library If you want to use a different serialization library throughout your service, we suggest implementing [`SerdeFactory`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/serde/SerdeFactory) and annotating the service class with [`@CustomSerdeFactory`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/annotation/CustomSerdeFactory). Refer to the Javadocs for more details. # Service Communication Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/service-communication Call other services from your handler. Your Restate handler can call other handlers in three ways: * **[Request-response calls](#request-response-calls)**: Call and wait for a response * **[One-way messages](#sending-messages)**: Send a message and continue * **[Delayed messages](#delayed-messages)**: Send a message after a delay To call a service from an external application, see the [HTTP](/services/invocation/http), [Kafka](/services/invocation/kafka), or [SDK Clients](/services/invocation/clients/java-sdk) documentation. [Why use Restate for service communication?](/foundations/key-concepts#resilient-communication) ## Generating service clients The Restate Java SDK automatically generates clients for each of your services when you build the project. If you don't see the generated clients, make sure you [added the code generator](develop/java/overview#getting-started) and have built the project with `./gradlew build` or `mvn compile exec:java`. ## Request-response calls To call a Restate handler, use the generated clients and wait for its result: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#request_response"} theme={null} // To call a Service: String svcResponse = MyServiceClient.fromContext(ctx).myHandler(request).await(); // To call a Virtual Object: String objResponse = MyObjectClient.fromContext(ctx, objectKey).myHandler(request).await(); // To call a Workflow: // `run` handler — can only be called once per workflow ID String wfResponse = MyWorkflowClient.fromContext(ctx, workflowId).run(request).await(); // Other handlers can be called anytime within workflow retention String status = MyWorkflowClient.fromContext(ctx, workflowId).interactWithWorkflow(request).await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#request_response"} theme={null} // To call a Service: val svcResponse = MyServiceClient.fromContext(ctx).myHandler(request).await() // To call a Virtual Object: val objResponse = MyObjectClient.fromContext(ctx, objectKey).myHandler(request).await() // To call a Workflow: // `run` handler — can only be called once per workflow ID val wfResponse = MyWorkflowClient.fromContext(ctx, workflowId).run(request).await() // Other handlers can be called anytime within workflow retention MyWorkflowClient.fromContext(ctx, workflowId).interactWithWorkflow(request).await() ``` Use the generic clients when you don't have access to typed clients or need dynamic service names: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#request_response_generic"} theme={null} Target target = Target.service("MyService", "myHandler"); // or virtualObject or workflow String response = ctx.call(Request.of(target, TypeTag.of(String.class), TypeTag.of(String.class), request)) .await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#request_response_generic"} theme={null} val target = Target.service("MyService", "myHandler") val response = ctx.call(Request.of(target, typeTag(), typeTag(), request)).await() ``` After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). Update this via the [service configuration](/services/configuration). Request-response calls between [exclusive handlers](/foundations/handlers#handler-behavior) of Virtual Objects may lead to deadlocks: * Cross deadlock: A → B and B → A (same keys). * Cycle deadlock: A → B → C → A. Use the UI or CLI to [cancel](/services/invocation/managing-invocations#cancel) and unblock deadlocked invocations. ## Sending messages To send a message to another Restate handler without waiting for a response: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#one_way"} theme={null} MyServiceClient.fromContext(ctx).send().myHandler(request); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#one_way"} theme={null} MyServiceClient.fromContext(ctx).send().myHandler(request) ``` Restate handles message delivery and retries, so the handler can complete and return without waiting for the message to be processed. Use generic clients when you don't have the service definition: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#one_way_generic"} theme={null} Target target = Target.service("MyService", "myHandler"); // or virtualObject or workflow ctx.send(Request.of(target, TypeTag.of(String.class), TypeTag.of(String.class), request)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#one_way_generic"} theme={null} val target = Target.service("MyService", "myHandler") ctx.send(Request.of(target, typeTag(), typeTag(), request)) ``` Calls to a Virtual Object execute in order of arrival, serially. Example: ```java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#ordering"} theme={null} MyObjectClient.fromContext(ctx, objectKey).send().myHandler("I'm call A"); MyObjectClient.fromContext(ctx, objectKey).send().myHandler("I'm call B"); ``` Call A is guaranteed to execute before B. However, other invocations may interleave between A and B. ## Delayed messages To send a message after a delay, use the generated clients with `.send()` and the `Duration` as second parameter: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#delayed"} theme={null} MyServiceClient.fromContext(ctx).send().myHandler(request, Duration.ofDays(5)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#delayed"} theme={null} MyServiceClient.fromContext(ctx).send().myHandler(request, 5.days) ``` Or with the generic clients: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#delayed_generic"} theme={null} Target target = Target.service("MyService", "myHandler"); // or virtualObject or workflow ctx.send( Request.of(target, TypeTag.of(String.class), TypeTag.of(String.class), request), Duration.ofDays(5)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#delayed_generic"} theme={null} val target = Target.service("MyService", "myHandler") Request.of(target, typeTag(), typeTag(), request).send(ctx, 5.days) ``` Learn [how this is different](/develop/java/durable-timers#scheduling-async-tasks) from sleeping and then sending a message. ## Using an idempotency key To prevent duplicate executions of the same call, add an idempotency key: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#idempotency_key"} theme={null} // For a request-response call MyServiceClient.fromContext(ctx).myHandler(request, req -> req.idempotencyKey("abc123")); // For a message MyServiceClient.fromContext(ctx).send().myHandler(request, req -> req.idempotencyKey("abc123")); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#idempotency_key"} theme={null} // For a regular call MyServiceClient.fromContext(ctx).myHandler(request) { idempotencyKey = "abc123" } // For a one way call MyServiceClient.fromContext(ctx).send().myHandler(request) { idempotencyKey = "abc123" } ``` Restate automatically deduplicates calls made during the same handler execution, so there's no need to provide an idempotency key in that case. However, if multiple handlers might call the same service independently, you can use an idempotency key to ensure deduplication across those calls. ## Attach to an invocation To wait for or get the result of a previously sent message: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#attach"} theme={null} var handle = MyServiceClient.fromContext(ctx) .send() .myHandler(request, req -> req.idempotencyKey("abc123")); var response = handle.attach().await(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#attach"} theme={null} val handle = MyServiceClient.fromContext(ctx).send().myHandler(request) { idempotencyKey = "abc123" } val response = handle.attach().await() ``` * With an idempotency key: Wait for completion and retrieve the result. * Without an idempotency key: Can only wait, not retrieve the result. ## Cancel an invocation To cancel a running handler: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServiceCommunication.java#cancel"} theme={null} var handle = MyServiceClient.fromContext(ctx).send().myHandler(request); handle.cancel(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServiceCommunication.kt#cancel"} theme={null} val handle = MyServiceClient.fromContext(ctx).send().myHandler(request) handle.cancel() ``` ## See also * **[SDK Clients](/develop/java/service-communication)**: Call Restate services from external applications * **[Error Handling](/develop/java/error-handling)**: Handle failures and terminal errors in service calls * **[Durable Timers](/develop/java/durable-timers)**: Implement timeouts for your service calls * **[Serialization](/develop/java/serialization)**: Customize how data is serialized between services * **[Sagas](/guides/sagas)**: Roll back or compensate for canceled service calls. # Services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/services Implementing Restate services with the Java/Kotlin SDK. The Restate Java/Kotlin SDK is open source and available on [GitHub](https://github.com/restatedev/sdk-java). The Restate SDK lets you implement **handlers**. Handlers can be part of a **[Basic Service](/foundations/services#basic-service)**, a **[Virtual Object](/foundations/services#virtual-object)**, or a **[Workflow](/foundations/services#workflow)**. This page shows how to define them with the Java/Kotlin SDK. ## Prerequisites * [JDK](https://whichjdk.com/) >= 17 Contact us on [Discord](https://discord.com/invite/skW3AZ6uGd)/[Slack](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA) if you need earlier Java versions. ## Getting started Get started quickly with the [Java](/quickstart#java) or [Kotlin](/quickstart#kotlin) Quickstart. Use the following dependencies: ```kt Java theme={null} // Annotation processor annotationProcessor("dev.restate:sdk-api-gen:2.4.0") // For HTTP services implementation("dev.restate:sdk-java-http:2.4.0") // Or for Lambda services implementation("dev.restate:sdk-java-lambda:2.4.0") ``` ```kt Kotlin theme={null} // Annotation processor ksp("dev.restate:sdk-api-kotlin-gen:2.4.0") // For HTTP services implementation("dev.restate:sdk-kotlin-http:2.4.0") // Or for Lambda services // implementation("dev.restate:sdk-kotlin-lambda:2.4.0") ``` ## Basic Services [Basic Services](/foundations/services) group related **handlers** and expose them as callable endpoints: ```java Java {"CODE_LOAD::java/src/main/java/develop/MyService.java?collapse_prequel"} theme={null} @Service public class MyService { @Handler public String myHandler(Context ctx, String greeting) { return greeting + "!"; } public static void main(String[] args) { RestateHttpServer.listen(Endpoint.bind(new MyService())); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyService.kt?collapse_prequel"} theme={null} @Service class MyService { @Handler suspend fun myHandler(ctx: Context, greeting: String) = "$greeting!" } fun main() { RestateHttpServer.listen(endpoint { bind(MyService()) }) } ``` * Define a service using the [`@Service`](http://docs.restate.dev/javadocs/dev/restate/sdk/annotation/Service.html) and [`@Handler`](http://docs.restate.dev/javadocs/dev/restate/sdk/annotation/Handler.html) annotations * Each handler can be called at `/MyService/myHandler`. To override the service name (default is simple class name), use the annotation [`@Name`](http://docs.restate.dev/javadocs/dev/restate/sdk/annotation/Name.html). * Handlers take the `Context` ([JavaDocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/ObjectContext)/[KotlinDocs](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin/-context/)) as the first argument. * The input parameter (at most one) and return type are optional and can be of any type. See [serialization](/develop/java/serialization) for more details. * Create an endpoint to expose the service over HTTP (port `9080` by default). ## Virtual Objects [Virtual Objects](/foundations/services) are services that are stateful and key-addressable — each object instance has a unique ID and persistent state. ```java Java {"CODE_LOAD::java/src/main/java/develop/MyObject.java?collapse_prequel"} theme={null} @VirtualObject public class MyObject { @Handler public String myHandler(ObjectContext ctx, String greeting) { String objectId = ctx.key(); return greeting + " " + objectId + "!"; } @Shared public String myConcurrentHandler(SharedObjectContext ctx, String input) { return "my-output"; } public static void main(String[] args) { RestateHttpServer.listen(Endpoint.bind(new MyObject())); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyObject.kt?collapse_prequel"} theme={null} @VirtualObject class MyObject { @Handler suspend fun myHandler(ctx: ObjectContext, greeting: String): String { val objectKey = ctx.key() return "$greeting $objectKey!" } @Shared suspend fun myConcurrentHandler(ctx: SharedObjectContext, input: String) = "my-output" } fun main() { RestateHttpServer.listen(endpoint { bind(MyObject()) }) } ``` * Use the `@VirtualObject` annotation. * The first argument of the handler must be the `ObjectContext` parameter ([JavaDocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/ObjectContext)/[KotlinDocs](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin/-object-context/)). * Each instance is identified by a key (accessible via `ctx.key()`). * Virtual Objects can have [exclusive and shared handlers](/foundations/handlers#handler-behavior). * Exclusive handlers receive an `ObjectContext`, allowing read/write access to object state. * Shared handlers use the [`@Shared`](http://docs.restate.dev/javadocs/dev/restate/sdk/annotation/Shared.html) annotation and the `SharedObjectContext` ([JavaDocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/SharedObjectContext)/[KotlinDocs](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin/-shared-object-context/)). ## Workflows [Workflows](/foundations/services) are long-lived processes with a defined lifecycle. They run once per key and are ideal for orchestrating multi-step operations, which require external interaction via signals and queries. ```java Java {"CODE_LOAD::java/src/main/java/develop/MyWorkflow.java?collapse_prequel"} theme={null} @Workflow public class MyWorkflow { @Workflow public String run(WorkflowContext ctx, String input) { // implement workflow logic here return "success"; } @Shared public String interactWithWorkflow(SharedWorkflowContext ctx, String input) { // implement interaction logic here return "my result"; } public static void main(String[] args) { RestateHttpServer.listen(Endpoint.bind(new MyWorkflow())); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyWorkflow.kt?collapse_prequel"} theme={null} @Workflow class MyWorkflow { @Workflow suspend fun run(ctx: WorkflowContext, input: String): String { // implement workflow logic here return "success" } @Handler suspend fun interactWithWorkflow(ctx: SharedWorkflowContext, input: String): String { // implement interaction logic here return "my result" } } fun main() { RestateHttpServer.listen(endpoint { bind(MyWorkflow()) }) } ``` * Create the workflow by using the [`@Workflow`](http://docs.restate.dev/javadocs/dev/restate/sdk/annotation/Workflow.html) annotation. * Every workflow **must** include a `run` handler: * This is the main orchestration entry point * It runs exactly once per workflow execution and uses the `WorkflowContext` ([JavaDocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/WorkflowContext)/[KotlinDocs](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin/-workflow-context/)) * Resubmission of the same workflow will fail with "Previously accepted". The invocation ID can be found in the request header `x-restate-id`. * Use `ctx.key()` to access the workflow's unique ID * Additional handlers must use the `SharedWorkflowContext` ([JavaDocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/SharedWorkflowContext)/[KotlinDocs](https://restatedev.github.io/sdk-java/ktdocs/sdk-api-kotlin/dev.restate.sdk.kotlin/-shared-workflow-context/)) and can signal or query the workflow. They can run concurrently with the `run` handler and until the retention time expires. ## Configuring services Check out the [service configuration docs](/services/configuration) to learn how to configure service behavior, including timeouts and retention policies. # Serving Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/serving Create an endpoint to serve your services. export const CustomVars = ({name}) => { const RESTATE_VERSION = "1.4"; const TYPESCRIPT_SDK_VERSION = "1.7.2"; const JAVA_SDK_VERSION = "2.4.0"; const GO_SDK_VERSION = "0.18.0"; const PYTHON_SDK_VERSION = "0.8.0"; const RUST_SDK_VERSION = "0.6.0"; const mapping = { RESTATE_VERSION, TYPESCRIPT_SDK_VERSION, JAVA_SDK_VERSION, GO_SDK_VERSION, PYTHON_SDK_VERSION, RUST_SDK_VERSION, JAVA_HTTP_DEPENDENCY: `dev.restate:sdk-java-http:${JAVA_SDK_VERSION}`, KOTLIN_HTTP_DEPENDENCY: `dev.restate:sdk-kotlin-http:${JAVA_SDK_VERSION}`, JAVA_LAMBDA_DEPENDENCY: `dev.restate:sdk-java-lambda:${JAVA_SDK_VERSION}`, KOTLIN_LAMBDA_DEPENDENCY: `dev.restate:sdk-kotlin-lambda:${JAVA_SDK_VERSION}`, JAVA_SDK_REQUEST_IDENTITY: `dev.restate:sdk-request-identity:${JAVA_SDK_VERSION}`, JAVA_TESTING: `dev.restate:sdk-testing:${JAVA_SDK_VERSION}`, JAVA_CLIENT: `dev.restate:client:${JAVA_SDK_VERSION}`, KOTLIN_CLIENT: `dev.restate:client-kotlin:${JAVA_SDK_VERSION}`, DENO_FETCH: `npm:@restatedev/restate-sdk@^${TYPESCRIPT_SDK_VERSION}/fetch` }; return mapping[name]; }; Restate services can run in two ways: as an HTTP endpoint or as AWS Lambda functions. ## Creating an HTTP endpoint 1. Use either {} or {} as SDK dependency. 2. Create an endpoint 3. Bind one or multiple services to it 4. Listen on the specified port (default `9080`) for connections and requests. ```java Java {"CODE_LOAD::java/src/main/java/develop/ServingHttp.java#here"} theme={null} import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.http.vertx.RestateHttpServer; class MyApp { public static void main(String[] args) { RestateHttpServer.listen( Endpoint.bind(new MyService()).bind(new MyObject()).bind(new MyWorkflow()), 8080); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServingHttp.kt#here"} theme={null} fun main() { RestateHttpServer.listen( endpoint { bind(MyService()) bind(MyObject()) bind(MyWorkflow()) }) } ``` ## Creating a Lambda handler 1. Use either {} or {} as SDK dependency. 2. Extend the class `BaseRestateLambdaHandler` 3. Override the register method 4. Bind one or multiple services to the builder ```java Java {"CODE_LOAD::java/src/main/java/develop/ServingLambda.java#here"} theme={null} import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.lambda.BaseRestateLambdaHandler; class MyLambdaHandler extends BaseRestateLambdaHandler { @Override public void register(Endpoint.Builder builder) { builder.bind(new MyService()).bind(new MyObject()); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyLambdaHandler.kt#here"} theme={null} import dev.restate.sdk.endpoint.Endpoint import dev.restate.sdk.lambda.BaseRestateLambdaHandler class MyLambdaHandler : BaseRestateLambdaHandler() { override fun register(builder: Endpoint.Builder) { builder.bind(MyService()).bind(MyObject()) } } ``` The implementation of your services and handlers remains the same for both deployment options. Have a look at the [deployment section](/services/deploy/lambda) for guidance on how to deploy your services on AWS Lambda. If you use a JVM >= 21, you can use virtual threads to run your services: ```java Java {"CODE_LOAD::java/src/main/java/develop/ServingVirtualThreads.java#here"} theme={null} builder.bind( new Greeter(), HandlerRunner.Options.withExecutor(Executors.newVirtualThreadPerTaskExecutor())); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServingVirtualThreads.kt#here"} theme={null} builder.bind( Greeter(), HandlerRunner.Options( coroutineContext = Executors.newVirtualThreadPerTaskExecutor().asCoroutineDispatcher(), ), ) ``` ## Validating request identity SDKs can validate that incoming requests come from a particular Restate instance. You can find out more about request identity in the [Security docs](/services/security#locking-down-service-access). You will need to use the request identity dependency {}. ```java Java {"CODE_LOAD::java/src/main/java/develop/ServingIdentity.java#here"} theme={null} import dev.restate.sdk.auth.signing.RestateRequestIdentityVerifier; import dev.restate.sdk.endpoint.Endpoint; import dev.restate.sdk.http.vertx.RestateHttpServer; class MySecureApp { public static void main(String[] args) { var endpoint = Endpoint.bind(new MyService()) .withRequestIdentityVerifier( RestateRequestIdentityVerifier.fromKeys( "publickeyv1_w7YHemBctH5Ck2nQRQ47iBBqhNHy4FV7t2Usbye2A6f")); RestateHttpServer.listen(endpoint); } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ServingIdentity.kt#here"} theme={null} import dev.restate.sdk.auth.signing.RestateRequestIdentityVerifier import dev.restate.sdk.http.vertx.RestateHttpServer import dev.restate.sdk.kotlin.endpoint.endpoint fun main() { RestateHttpServer.listen( endpoint { bind(MyService()) // !mark(1:3) requestIdentityVerifier = RestateRequestIdentityVerifier.fromKeys( "publickeyv1_w7YHemBctH5Ck2nQRQ47iBBqhNHy4FV7t2Usbye2A6f", ) }) } ``` # State Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/state Store key-value state in Restate. Restate lets you persist key-value (K/V) state using its embedded K/V store. ## Key characteristics [Learn about Restate's embedded K/V store](/foundations/key-concepts#consistent-state). **State is only available for Virtual Objects and Workflows.** Scope & retention: * For Virtual Objects: State is scoped per object key and retained indefinitely. It is persisted and shared across all invocations for that object until explicitly cleared. * For Workflows: State is scoped per workflow execution (workflow ID) and retained only for the duration of the workflow’s configured retention time. Access Rules: * [Exclusive handlers](/foundations/handlers#handler-behavior) (e.g., `run()` in workflows) can read and write state. * [Shared handlers](/foundations/handlers#handler-behavior) can only read state and cannot mutate it. You can inspect and edit the K/V state via the UI and the [CLI](/services/introspection#inspecting-application-state). ## List all state keys To retrieve all keys for which the current Virtual Object has stored state: ```java Java {"CODE_LOAD::java/src/main/java/develop/State.java#statekeys"} theme={null} Collection keys = ctx.stateKeys(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/State.kt#statekeys"} theme={null} val keys = ctx.stateKeys() ``` ## Get state value To read a value by key. ```java Java {"CODE_LOAD::java/src/main/java/develop/State.java#get"} theme={null} // Getting String value StateKey STRING_STATE_KEY = StateKey.of("my-key", String.class); String stringState = ctx.get(STRING_STATE_KEY).orElse("my-default"); // Getting integer value StateKey INT_STATE_KEY = StateKey.of("my-key", Integer.class); int intState = ctx.get(INT_STATE_KEY).orElse(0); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/State.kt#get"} theme={null} // Getting String value val STRING_STATE_KEY = stateKey("my-key") val stringState: String? = ctx.get(STRING_STATE_KEY) // Getting integer value val INT_STATE_KEY = stateKey("my-key") val intState: Int? = ctx.get(INT_STATE_KEY) ``` See the [serialization docs](/develop/java/serialization) to customize the state serializer. ## Set state value To write or update a value: ```java Java {"CODE_LOAD::java/src/main/java/develop/State.java#set"} theme={null} StateKey STRING_STATE_KEY = StateKey.of("my-key", String.class); ctx.set(STRING_STATE_KEY, "my-new-value"); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/State.kt#set"} theme={null} val STRING_STATE_KEY = stateKey("my-key") ctx.set(STRING_STATE_KEY, "my-new-value") ``` ## Clear state key To delete a specific key: ```java Java {"CODE_LOAD::java/src/main/java/develop/State.java#clear"} theme={null} StateKey STRING_STATE_KEY = StateKey.of("my-key", String.class); ctx.clear(STRING_STATE_KEY); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/State.kt#clear"} theme={null} val STRING_STATE_KEY = stateKey("my-key") ctx.clear(STRING_STATE_KEY) ``` ## Clear all state keys To remove all stored state for the current Virtual Object: ```java Java {"CODE_LOAD::java/src/main/java/develop/State.java#clear_all"} theme={null} ctx.clearAll(); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/State.kt#clear_all"} theme={null} ctx.clearAll() ``` ## Advanced: Eager vs. lazy state loading Restate supports two modes for loading state in handlers: ### Eager state (default) * **How it works**: State is automatically sent with the request when invoking a handler * **Benefits**: State is available immediately when the handler starts executing * **Behavior**: All reads and writes to state are local to the handler execution * **Best for**: Small to medium state objects that are frequently accessed ### Lazy state * **How it works**: State is fetched on-demand on `get` calls from the Restate Server * **Benefits**: Reduces initial request size and memory usage * **Setup**: Enable lazy state in the [service or handler configuration](/services/configuration) * **Best for**: Large state objects that aren't needed in every handler execution # Testing Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/java/testing Utilities to test your handler logic. export const CustomVars = ({name}) => { const RESTATE_VERSION = "1.4"; const TYPESCRIPT_SDK_VERSION = "1.7.2"; const JAVA_SDK_VERSION = "2.4.0"; const GO_SDK_VERSION = "0.18.0"; const PYTHON_SDK_VERSION = "0.8.0"; const RUST_SDK_VERSION = "0.6.0"; const mapping = { RESTATE_VERSION, TYPESCRIPT_SDK_VERSION, JAVA_SDK_VERSION, GO_SDK_VERSION, PYTHON_SDK_VERSION, RUST_SDK_VERSION, JAVA_HTTP_DEPENDENCY: `dev.restate:sdk-java-http:${JAVA_SDK_VERSION}`, KOTLIN_HTTP_DEPENDENCY: `dev.restate:sdk-kotlin-http:${JAVA_SDK_VERSION}`, JAVA_LAMBDA_DEPENDENCY: `dev.restate:sdk-java-lambda:${JAVA_SDK_VERSION}`, KOTLIN_LAMBDA_DEPENDENCY: `dev.restate:sdk-kotlin-lambda:${JAVA_SDK_VERSION}`, JAVA_SDK_REQUEST_IDENTITY: `dev.restate:sdk-request-identity:${JAVA_SDK_VERSION}`, JAVA_TESTING: `dev.restate:sdk-testing:${JAVA_SDK_VERSION}`, JAVA_CLIENT: `dev.restate:client:${JAVA_SDK_VERSION}`, KOTLIN_CLIENT: `dev.restate:client-kotlin:${JAVA_SDK_VERSION}`, DENO_FETCH: `npm:@restatedev/restate-sdk@^${TYPESCRIPT_SDK_VERSION}/fetch` }; return mapping[name]; }; The Java SDK comes with the module `sdk-testing` that integrates with JUnit 5 and [Testcontainers](https://testcontainers.com/) to start up a Restate container together with your services code and automatically register them. Add the following dependency to your project: {}. ## Using the JUnit 5 Extension Given the service to test `MyService`, annotate the test class with `@RestateTest` and annotate the services to bind with `@BindService`: ```java Java {"CODE_LOAD::java/src/main/java/develop/MyServiceTest.java#extension"} theme={null} @RestateTest class MyServiceTest { @BindService MyService service = new MyService(); // Your tests } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyServiceTest.kt#extension"} theme={null} @RestateTest class MyServiceTest { @BindService val service = MyService() // Your tests } ``` Note that the extension will start one Restate server for the whole test class. For more details, checkout [`RestateTest` Javadocs](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/testing/RestateTest.html). Once the extension is set, you can implement your test methods as usual, and inject a [`Client`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/client/Client.html) using [`@RestateClient`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/testing/RestateClient.html) to interact with Restate and the registered services: ```java Java {"CODE_LOAD::java/src/main/java/develop/MyServiceTestMethod.java#test"} theme={null} @Test void testMyHandler(@RestateClient Client ingressClient) { // Create the service client from the injected ingress client var client = MyServiceClient.fromClient(ingressClient); // Send request to service and assert the response var response = client.myHandler("Hi"); assertEquals(response, "Hi!"); } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/MyServiceTestMethod.kt#test"} theme={null} @Test fun testMyHandler(@RestateClient ingressClient: Client) = runTest { // Create the service client from the injected ingress client val client = MyServiceClient.fromClient(ingressClient) // Send request to service and assert the response val response = client.myHandler("Hi") assertEquals(response, "Hi!") } ``` ## Usage without JUnit 5 You can use the testing tools without JUnit 5 by using [`RestateRunner`](https://restatedev.github.io/sdk-java/javadocs/dev/restate/sdk/testing/RestateRunner.html) directly. For more details, refer to the Javadocs. # Concurrent Tasks Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/concurrent-tasks Execute multiple tasks concurrently and gather results. When building resilient applications, you often need to perform multiple operations in parallel to improve performance and user experience. Restate provides durable concurrency primitives that allow you to run tasks concurrently while maintaining deterministic execution during replays. ## When to use concurrent tasks Use concurrent tasks when you need to: * Call multiple external services simultaneously (e.g., fetching data from different APIs) * Race multiple operations and use the first result (e.g., trying multiple LLM providers) * Implement timeouts by racing an operation against a timer * Perform batch operations where individual tasks can run in parallel ## Key benefits * **Deterministic replay**: Restate logs the order of completion, ensuring consistent behavior during failures * **Fault tolerance**: If your handler fails, tasks that were already completed will be replayed with their results, while pending tasks will be retried ## Parallelizing tasks Start multiple durable operations concurrently by calling them without immediately awaiting: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#parallel"} theme={null} # Start operations concurrently call1 = ctx.run_typed("fetch_user", fetch_user_data, user_id=123) call2 = ctx.run_typed("fetch_orders", fetch_order_history, user_id=123) call3 = ctx.service_call(calculate_metrics, arg=123) # Now wait for results as needed user = await call1 orders = await call2 metrics = await call3 ``` Check out the guide on [parallelizing work](guides/parallelizing-work). ## Retrieving results Restate provides several patterns for coordinating concurrent tasks. All patterns use `RestateDurableFuture` combinators that log the order of completion, ensuring deterministic behavior during replays. ### Waiting for first completion There are two ways to do this. #### Select Use `restate.select()` to race multiple operations and handle the first one that completes. This is ideal for implementing timeouts or waiting for external confirmations: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#select"} theme={null} _, confirmation_future = ctx.awakeable(type_hint=str) match await restate.select( confirmation=confirmation_future, timeout=ctx.sleep(timedelta(days=1)) ): case ["confirmation", "ok"]: return "success!" case ["confirmation", "deny"]: raise TerminalError("Confirmation was denied!") case _: raise TerminalError("Verification timer expired!") ``` #### Wait completed Use `restate.wait_completed()` when you want to wait for at least one task to complete. This returns a tuple of two lists: the first list contains the futures that are completed, the second list contains the futures that are not completed. This gives you the option to, for example, cancel the pending futures: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#wait_completed"} theme={null} claude = ctx.service_call(claude_sonnet, arg=f"What is the weather?") openai = ctx.service_call(open_ai, arg=f"What is the weather?") pending, done = await restate.wait_completed(claude, openai) # collect the completed results results = [await f for f in done] # cancel the pending calls for f in pending: await f.cancel_invocation() ``` #### Key Differences * **Return behavior**: `select` returns as soon as the first future completes, while `wait_completed` waits for at least one to complete and returns both completed and pending futures. * **Use cases**: * Use `select` when you want to race multiple operations and act on whichever completes first. * Use `wait_completed` when you want to handle completed futures immediately while potentially canceling or managing pending ones, * **Pattern matching**: `select` uses pattern matching to determine which future completed, while `wait_completed` separates futures into completed and pending collections. ### Waiting for all tasks to complete ```python {"CODE_LOAD::python/src/develop/journaling_results.py#all"} theme={null} claude = ctx.service_call(claude_sonnet, arg=f"What is the weather?") openai = ctx.service_call(open_ai, arg=f"What is the weather?") results_done = await restate.gather(claude, openai) results = [await result for result in results_done] ``` ### Processing results as they complete Use `restate.as_completed()` to process results in the order they finish: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#as_completed"} theme={null} call1 = ctx.run_typed( "LLM call", call_llm, prompt="What is the weather?", model="gpt-4" ) call2 = ctx.run_typed( "LLM call", call_llm, prompt="What is the weather?", model="gpt-3.5-turbo" ) async for future in restate.as_completed(call1, call2): # do something with the completed future print(await future) ``` # Durable Steps Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/durable-steps Persist results of operations. Restate uses an execution log to replay operations after failures and suspensions. Non-deterministic operations (database calls, HTTP requests, UUID generation) must be wrapped to ensure deterministic replay. ## Run Use `ctx.run` to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate store its result in the execution log. ```python {"CODE_LOAD::python/src/develop/journaling_results.py#side_effect"} theme={null} async def call_llm(prompt: str) -> str: # ... implement ... return "llm response" # specify the (async) function to call and its arguments result = await ctx.run_typed("LLM call", call_llm, prompt="What is the weather?") # or use a lambda to capture a single value my_number = await ctx.run_typed("generate number", lambda: random.randint(0, 10)) ``` Note that inside `ctx.run`, you cannot use the Restate context (e.g., `ctx.get`, `ctx.sleep`, or nested `ctx.run`). By default, the SDK serializes the journal entry with the [`json`](https://docs.python.org/3/library/json.html#) library. Alternatively, you can specify a [Pydantic model](/develop/python/serialization#pydantic) or [custom serializer](/develop/python/serialization#custom-serialization). Failures in `ctx.run` are treated the same as any other handler error. Restate will retry it unless configured otherwise or unless a [`TerminalError`](/develop/python/error-handling) is thrown. You can customize how `ctx.run` retries via: ```py {"CODE_LOAD::python/src/develop/retries.py#here"} theme={null} try: retry_opts = restate.RunOptions( max_attempts=10, max_retry_duration=timedelta(seconds=30) ) await ctx.run_typed("write", write_to_other_system, retry_opts) except TerminalError as err: # Handle the terminal error after retries exhausted # For example, undo previous actions (see sagas guide) and # propagate the error back to the caller raise err ``` * You can limit retries by time or count * When the policy is exhausted, a `TerminalError` is thrown * See the [Error Handling Guide](/guides/error-handling) and the [Sagas Guide](/guides/sagas) for patterns like compensation If Restate doesn't receive new journal entries from a service for more than one minute (by default), it will automatically abort the invocation and retry it. However, some business logic can take longer to complete—for example, an LLM call that takes up to 3 minutes to respond. In such cases, you can adjust the service’s [abort timeout and inactivity timeout](/services/configuration) settings to accommodate longer execution times. For more information, see the [error handling guide](/guides/error-handling). ## Deterministic randoms When you do non-deterministic operations, like generating UUIDs or random numbers, you must ensure that the results are deterministic on replay. For example, to generate stable UUIDs for things like idempotency keys: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#uuid"} theme={null} my_uuid = ctx.uuid() ``` The SDK provides deterministic helpers for random values — seeded by the invocation ID — so they return the **same result on retries**. ### UUIDs To generate stable UUIDs for things like idempotency keys: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#uuid"} theme={null} my_uuid = ctx.uuid() ``` Do not use this in cryptographic contexts. ### Random numbers To generate a deterministic float between `0` and `1`: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#random_nb"} theme={null} ctx.random().random() ``` This behaves like `Math.random()` but is deterministically replayable. ### Deterministic time To get the current millis since midnight, January 1, 1970, that is consistent across retries: ```python {"CODE_LOAD::python/src/develop/journaling_results.py#time"} theme={null} current_time = await ctx.time() ``` # Scheduling & Timers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/durable-timers Durable timers, scheduled actions, and sleep, backed by Restate. Restate provides durable, fault-tolerant timers that allow you to: * [Sleep](#durable-sleep): Pause a handler for a given duration * [Send delayed messages](#scheduling-async-tasks): Schedule handler invocations in the future * [Set timeouts](#timers-and-timeouts) for async operations * Implement patterns like [cron jobs](#cron-jobs) ## How it works Restate tracks and manages timers, ensuring they survive failures and restarts. For example, if a service is sleeping for 12 hours and fails after 8 hours, then Restate will make sure it only sleeps 4 hours more. If your handler runs on function-as-a-service platforms like AWS Lambda, Restate suspends the handler while it is sleeping, to free up resources. Since these platforms charge on execution time, this saves costs. ## Durable sleep To pause a handler for a set duration: ```python {"CODE_LOAD::python/src/develop/durable_timers.py#here"} theme={null} await ctx.sleep(delta=timedelta(seconds=10)) ``` There is no hard limit on how long you can sleep. You can sleep for months or even years, but keep in mind: * If you sleep in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. * You need to keep the deployment version until the invocation completes (see [versioning](/services/versioning)). So for long sleeps, we recommend breaking this up into multiple handlers that call each other with [delayed messages](/develop/python/service-communication#delayed-messages). The Restate SDK calculates the wake-up time based on the delay you specify. The Restate Server then uses this calculated time to wake up the handler. If the Restate Server and the SDK have different system clocks, the sleep duration might not be accurate. So make sure that the system clock of the Restate Server and the SDK have the same timezone and are synchronized. A mismatch can cause timers to fire earlier or later than expected. ## Scheduling async tasks To invoke a handler at a later time, use [delayed messages](/develop/python/service-communication#delayed-messages). In theory, you can schedule future work in Restate in two ways: 1. **Delayed messages** (recommended) 2. **Sleep + send** - sleeping in the current handler, then sending a message At first sight, both approaches might seem to achieve similar results. However, we recommend to use delayed messages for the following reasons: * **Handler completes immediately**: The calling handler can finish execution and complete the invocation without waiting for the delay to finish * **No Virtual Object blocking**: Other requests to the same Virtual Object can be processed during the delay period * **Better for service versioning**: No long-running invocations that require keeping old service deployments around for a long time (see [service versioning](/services/versioning)) ## Timers and timeouts Most context actions are async actions that return a `RestateDurableFuture`. You can race these against a timer to bound how long your code waits for an operation. For example, to either wait for the greeting or for the timeout: ```py {"CODE_LOAD::python/src/develop/durable_timers.py#timer"} theme={null} match await restate.select( greeting=ctx.service_call(my_service.my_handler, "Hi"), timeout=ctx.sleep(timedelta(seconds=5)), ): case ["greeting", greeting]: print("Greeting:", greeting) case _: print("Timeout occurred") ``` For alternative ways of racing async operations, have a look at the [`RestateDurableFuture` Combinators](/develop/python/concurrent-tasks). ## Cron jobs Restate does not yet include native cron support, but you can implement your own cron scheduler using: * Durable timers * Virtual Objects * A repeat loop or sleep-schedule pattern Check out the guide on [implementing cron jobs](/guides/cron). # Error Handling Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/error-handling Stop infinite retries with Terminal Errors. Restate handles retries for failed invocations. Check out the [Error Handling guide](/guides/error-handling) to learn more about how Restate handles transient errors, terminal errors, retries, and timeouts. ## Retry strategies By default, Restate does infinite retries with an exponential backoff strategy. Check out the [error handling guide](/guides/error-handling) to learn how to customize this. ## Terminal Errors For failures for which you do not want retries, but instead want the invocation to end and the error message to be propagated back to the caller, you can throw a **terminal error**. You can throw a `TerminalError` with an optional HTTP status code and a message anywhere in your handler, as follows: ```python {"CODE_LOAD::python/src/develop/error_handling.py#terminal"} theme={null} from restate.exceptions import TerminalError raise TerminalError("Something went wrong.") ``` You can catch terminal errors, and build your control flow around it. When you throw a terminal error, you might need to undo the actions you did earlier in your handler to make sure that your system remains in a consistent state. Have a look at our [sagas guide](/guides/sagas) to learn more. # External Events Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/external-events Handle external events and human-in-the-loop patterns with durable waiting primitives. Sometimes your handlers need to pause and wait for external processes to complete. This is common in: * **Human-in-the-loop workflows** (approvals, reviews, manual steps) * **External system integration** (waiting for webhooks, async APIs) * **AI agent patterns** (tool execution, human oversight) This pattern is also known as the **callback** or **task token** pattern. ## Two Approaches Restate provides two primitives for handling external events: | Primitive | Use Case | Key Feature | | -------------------- | -------------------------- | ------------------------------------ | | **Awakeables** | Services & Virtual Objects | Unique ID-based completion | | **Durable Promises** | Workflows only | Named promises for simpler signaling | ## How it works Implementing this pattern in a distributed system is tricky, since you need to ensure that the handler can recover from failures and resume waiting for the external event. Restate makes promises are durable and distributed. They survive crashes and can be resolved or rejected by any handler in the workflow. To save costs on FaaS deployments, Restate lets the handler [suspend](/foundations/key-concepts#suspensions-on-faas) while awaiting the promise, and invokes it again when the result is available. ## Awakeables **Best for:** Services and Virtual Objects where you need to coordinate with external systems. ### Creating and waiting for awakeables 1. **Create an awakeable** - Get a unique ID and promise 2. **Send the ID externally** - Pass the awakeable ID to your external system 3. **Wait for result** - Your handler [suspends](/foundations/key-concepts#suspensions-on-faas) until the external system responds ```py {"CODE_LOAD::python/src/develop/awakeables.py#here"} theme={null} id, promise = ctx.awakeable(type_hint=str) await ctx.run_typed("trigger task", request_human_review, name=name, id=id) review = await promise ``` By default, the SDK serializes the journal entry with the [`json`](https://docs.python.org/3/library/json.html#) library. Alternatively, you can specify a [Pydantic model](/develop/python/serialization#pydantic) or [custom serializer](/develop/python/serialization#custom-serialization). Note that if you wait for an awakeable in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. ### Resolving/rejecting Awakeables External processes complete awakeables in two ways: * **Resolve** with success data → handler continues normally * **Reject** with error reason → throws a [terminal error](/develop/python/error-handling) in the waiting handler #### Via SDK (from other handlers) **Resolve:** ```python {"CODE_LOAD::python/src/develop/awakeables.py#resolve"} theme={null} ctx.resolve_awakeable(name, review) ``` **Reject:** ```python {"CODE_LOAD::python/src/develop/awakeables.py#reject"} theme={null} ctx.reject_awakeable(name, "Cannot be reviewed") ``` #### Via HTTP API External systems can complete awakeables using Restate's HTTP API: **Resolve with data:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/resolve \ --json '"Looks good!"' ``` **Reject with error:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/reject \ -H 'content-type: text/plain' \ -d 'Review rejected: insufficient documentation' ``` ## Durable Promises **Best for:** Workflows where you need to signal between different workflow handlers. **Key differences from awakeables:** * No ID management - use logical names instead * Scoped to workflow execution lifetime Use this for: * Sending data to the run handler * Have handlers wait for events emitted by the run handler After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). The results of resolved Durable Promises remain available during this time. Update the retention time via the [service configuration](/services/configuration). ### Creating and waiting for promises Wait for a promise by name: ```py {"CODE_LOAD::python/src/develop/durable_promise.py#promise"} theme={null} review = await ctx.promise("review").value() ``` ### Resolving/rejecting promises Resolve/reject from any workflow handler: ```py {"CODE_LOAD::python/src/develop/durable_promise.py#resolve_promise"} theme={null} await ctx.promise("review").resolve(review) ``` ### Complete workflow example ```py expandable {"CODE_LOAD::python/src/develop/durable_promise.py#review"} theme={null} review_workflow = restate.Workflow("ReviewWorkflow") @review_workflow.main() async def run(ctx: restate.WorkflowContext, document_id: str): # Send document for review await ctx.run_typed("ask review", ask_review, document_id=document_id) # Wait for external review submission review = await ctx.promise("review").value() # Process the review result return process_review(document_id, review) @review_workflow.handler() async def submit_review(ctx: restate.WorkflowSharedContext, review: str): # Signal the waiting run handler await ctx.promise("review").resolve(review) app = restate.app([review_workflow]) ``` ### Two signaling patterns **External → Workflow** (shown above): External handlers signal the run handler * Use for human approvals, external API responses, manual interventions * External handlers call the handler which resolves the promise **Workflow → External**: Run handler signals other handlers waiting for workflow events * Use for step completion notifications, status updates, result broadcasting * Run handler resolves promises that external handlers are awaiting ## Best Practices * **Use awakeables** for services/objects coordinating with external systems * **Use durable promises** for workflow signaling * **Always handle rejections** to gracefully manage failures * **Include timeouts** for long-running external processes # Serialization Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/serialization Customize serialization for SDK actions. Restate sends data over the network for storing state, journaling actions, awakeables, etc. Therefore, Restate needs to serialize and deserialize the journal entries. ## Default Serialization By default, the SDK serializes the journal entry with the [`json`](https://docs.python.org/3/library/json.html#) library. If this does not work for your data type, then you need to specify a custom serializer, as shown below. ## Pydantic [Pydantic](https://docs.pydantic.dev/latest/) is a data validation and parsing library for Python. You can use Pydantic models to define the structure of your data: handler input/output, state, etc. ### Using Pydantic Make sure to install the optional `serde` dependency of the Restate SDK: `restate-sdk[serde]`. Then do the following: ```python {"CODE_LOAD::python/src/develop/serialization.py#using_pydantic"} theme={null} class Delivery(BaseModel): timestamp: datetime dimensions: tuple[int, int] class CompletedDelivery(BaseModel): status: str timestamp: datetime # For the input/output serialization of your handlers @my_object.handler() async def deliver(ctx: ObjectContext, delivery: Delivery) -> CompletedDelivery: # To get state await ctx.get("delivery", type_hint=Delivery) # To serialize awakeable payloads ctx.awakeable(type_hint=Delivery) # To serialize the results of actions await ctx.run_typed("some-task", do_something, restate.RunOptions(type_hint=Delivery)) return CompletedDelivery(status="delivered", timestamp=datetime.now()) ``` ### Pydantic & OpenAPI Pydantic integrates well with [OpenAPI](https://www.openapis.org/). Restate generates the OpenAPI specifications for you. If you use Pydantic, you can use the OpenAPI-generated clients to interact with your services. You can find example clients in the UI playground (click on your service in the overview and then on playground). ## Dataclasses You can also use Python's built-in `dataclasses` to define the structure of your data. Make sure to install the optional `serde` dependency of the Restate SDK `restate-sdk[serde]`. Then add a type hint in a similar way as you would with Pydantic. ## Custom Serialization To write a custom serializer, you implement the `Serde` interface. For example a custom JSON serializer could look like this: ```python {"CODE_LOAD::python/src/develop/serialization.py#custom"} theme={null} class MyData(typing.TypedDict): """Represents a response from the GPT model.""" some_value: str my_number: int class MySerde(Serde[MyData]): def deserialize(self, buf: bytes) -> typing.Optional[MyData]: if not buf: return None data = json.loads(buf) return MyData(some_value=data["some_value"], my_number=data["some_number"]) def serialize(self, obj: typing.Optional[MyData]) -> bytes: if obj is None: return bytes() data = {"some_value": obj["some_value"], "some_number": obj["my_number"]} return bytes(json.dumps(data), "utf-8") ``` You then use this serializer in your handlers, as follows: ```python {"CODE_LOAD::python/src/develop/serialization.py#using_custom_serde"} theme={null} # For the input/output serialization of your handlers @my_object.handler(input_serde=MySerde(), output_serde=MySerde()) async def my_handler(ctx: ObjectContext, greeting: str) -> str: # To serialize state await ctx.get("my_state", serde=MySerde()) ctx.set("my_state", MyData(some_value="Hi", my_number=15), serde=MySerde()) # To serialize awakeable payloads ctx.awakeable(serde=MySerde()) # etc. return "some-output" ``` # Service Communication Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/service-communication Call other services from your handler. Your Restate handler can call other handlers in three ways: * **[Request-response calls](#request-response-calls)**: Call and wait for a response * **[One-way messages](#sending-messages)**: Send a message and continue * **[Delayed messages](#delayed-messages)**: Send a message after a delay To call a service from an external application, see the [HTTP](/services/invocation/http) or [Kafka](/services/invocation/kafka) documentation. [Learn how Restate how it works](/foundations/key-concepts#resilient-communication) ## Request-response calls To call a Restate handler and wait for its result: ```python {"CODE_LOAD::python/src/develop/service_communication.py#request_response"} theme={null} # import my_service # Import the service module to get to the handler, not the service itself # To call a Service: response = await ctx.service_call(my_service.my_handler, arg="Hi") # To call a Virtual Object: response = await ctx.object_call(my_object.my_handler, key="Mary", arg="Hi") # To call a Workflow: # `run` handler — can only be called once per workflow ID response = await ctx.workflow_call(my_workflow.run, key="my_workflow_id", arg="Hi") # Other handlers can be called anytime within workflow retention response = await ctx.workflow_call( my_workflow.interact_with_workflow, key="my_workflow_id", arg="Hi" ) ``` Use generic calls when you don't have the service definition or need dynamic service names: ```python {"CODE_LOAD::python/src/develop/service_communication.py#request_response_generic"} theme={null} response = await ctx.generic_call( "MyObject", "my_handler", key="Mary", arg=json.dumps("Hi").encode("utf-8") ) ``` After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). Update this via the [service configuration](/services/configuration). Request-response calls between [exclusive handlers](/foundations/handlers#handler-behavior) of Virtual Objects may lead to deadlocks: * Cross deadlock: A → B and B → A (same keys). * Cycle deadlock: A → B → C → A. Use the UI or CLI to [cancel](/services/invocation/managing-invocations#cancel) and unblock deadlocked invocations. ## Sending messages To send a message to another Restate handler without waiting for a response: ```python {"CODE_LOAD::python/src/develop/service_communication.py#one_way"} theme={null} # To message a Service: ctx.service_send(my_service.my_handler, arg="Hi") # To message a Virtual Object: ctx.object_send(my_object.my_handler, key="Mary", arg="Hi") # To message a Workflow: # `run` handler — can only be called once per workflow ID ctx.workflow_send(my_workflow.run, key="my_wf_id", arg="Hi") # Other handlers can be called anytime within workflow retention ctx.workflow_send(my_workflow.interact_with_workflow, key="my_wf_id", arg="Hi") ``` Restate handles message delivery and retries, so the handler can complete and return without waiting for the message to be processed. Use generic send when you don't have the service definition: ```python {"CODE_LOAD::python/src/develop/service_communication.py#one_way_generic"} theme={null} ctx.generic_send("MyService", "my_handler", arg=json.dumps("Hi").encode("utf-8")) ``` Calls to a Virtual Object execute in order of arrival, serially. Example: ```python {"CODE_LOAD::python/src/develop/service_communication.py#ordering"} theme={null} ctx.object_send(my_object.my_handler, key="Mary", arg="I'm call A") ctx.object_send(my_object.my_handler, key="Mary", arg="I'm call B") ``` Call A is guaranteed to execute before B. However, other invocations may interleave between A and B. ## Delayed messages To send a message after a delay: ```python {"CODE_LOAD::python/src/develop/service_communication.py#delayed"} theme={null} # To message a Service with a delay: ctx.service_send(my_service.my_handler, arg="Hi", send_delay=timedelta(hours=5)) # To message a Virtual Object with a delay: ctx.object_send( my_object.my_handler, key="Mary", arg="Hi", send_delay=timedelta(hours=5) ) # To message a Workflow with a delay: ctx.workflow_send( my_workflow.run, key="my_workflow_id", arg="Hi", send_delay=timedelta(hours=5) ) ``` Use generic send with a delay when you don't have the service definition: ```python {"CODE_LOAD::python/src/develop/service_communication.py#delayed_generic"} theme={null} ctx.generic_send( "MyService", "my_handler", arg=json.dumps("Hi").encode("utf-8"), send_delay=timedelta(hours=5), ) ``` Learn [how this is different](/develop/python/durable-timers#scheduling-async-tasks) from sleeping and then sending a message. ## Using an idempotency key To prevent duplicate executions of the same call, add an idempotency key: ```python {"CODE_LOAD::python/src/develop/service_communication.py#idempotency_key"} theme={null} await ctx.service_call( my_service.my_handler, arg="Hi", idempotency_key="my-idempotency-key", ) ``` Restate automatically deduplicates calls made during the same handler execution, so there's no need to provide an idempotency key in that case. However, if multiple handlers might call the same service independently, you can use an idempotency key to ensure deduplication across those calls. ## Attach to an invocation To wait for or get the result of a previously sent message: ```python {"CODE_LOAD::python/src/develop/service_communication.py#attach"} theme={null} # Send a request, get the invocation id handle = ctx.service_send( my_service.my_handler, arg="Hi", idempotency_key="my-idempotency-key" ) invocation_id = await handle.invocation_id() # Now re-attach result = await ctx.attach_invocation(invocation_id) ``` * With an idempotency key: Wait for completion and retrieve the result. * Without an idempotency key: Can only wait, not retrieve the result. ## Cancel an invocation To cancel a running handler: ```python {"CODE_LOAD::python/src/develop/service_communication.py#cancel"} theme={null} ctx.cancel_invocation(invocation_id) ``` ## See also * **[Error Handling](/develop/python/error-handling)**: Handle failures and terminal errors in service calls * **[Durable Timers](/develop/python/durable-timers)**: Implement timeouts for your service calls * **[Serialization](/develop/python/serialization)**: Customize how data is serialized between services * **[Sagas](/guides/sagas)**: Roll back or compensate for canceled service calls. # Services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/services Implementing Restate services with the Python SDK. The Restate Python SDK is open source and available on [GitHub](https://github.com/restatedev/sdk-python). The Restate SDK lets you implement **handlers**. Handlers can be part of a **[Basic Service](/foundations/services#basic-service)**, a **[Virtual Object](/foundations/services#virtual-object)**, or a **[Workflow](/foundations/services#workflow)**. This page shows how to define them with the Python SDK. ## Prerequisites * Python >= v3.11 ## Getting started Get started quickly with the [Python Quickstart](/quickstart#python). Add the `restate_sdk[serde]` dependency to your Python project to start developing Restate services. ## Basic Services [Basic Services](/foundations/services) group related **handlers** and expose them as callable endpoints: ```python {"CODE_LOAD::python/src/develop/my_service.py"} theme={null} import restate my_service = restate.Service("MyService") @my_service.handler("myHandler") async def my_handler(ctx: restate.Context, greeting: str) -> str: return f"${greeting}!" app = restate.app([my_service]) ``` * Initialize the Service and specify the service name (here `MyService`). * Annotate each handler with `@my_service.handler()`. Optionally, you can override the handler name (here `myHandler`). * Each handler can then be called at `/myService/myHandler` * Handlers take the `restate.Context` as the first argument. * Handlers can take one optional JSON-serializable input and must return an optional output. These can be of any primitive type, `TypedDict` or Pydantic model (or see [custom serialization](/develop/python/serialization) for advanced types). * Finally, initialize the app, bind the service(s) to it, and serve it. Look at the [serving docs](/develop/python/serving) to learn more about serving your app. ## Virtual Objects [Virtual Objects](/foundations/services) are services that are stateful and key-addressable — each object instance has a unique ID and persistent state. ```python {"CODE_LOAD::python/src/develop/my_virtual_object.py"} theme={null} import restate my_object = restate.VirtualObject("MyVirtualObject") @my_object.handler("myHandler") async def my_handler(ctx: restate.ObjectContext, greeting: str) -> str: return f"${greeting} ${ctx.key()}!" @my_object.handler(kind="shared") async def my_concurrent_handler(ctx: restate.ObjectSharedContext, greeting: str) -> str: return f"${greeting} ${ctx.key()}!" app = restate.app([my_object]) ``` * Initialize a `restate.VirtualObject` and specify the object's name (here `MyVirtualObject`). * Each instance is identified by a key (accessible via `ctx.key()`). * Virtual Objects can have [exclusive and shared handlers](/foundations/handlers#handler-behavior). * Exclusive handlers receive an `ObjectContext`, allowing read/write access to object state. * Shared handlers have the annotation `kind="shared"` and receive an `ObjectSharedContext`. ## Workflows [Workflows](/foundations/services) are long-lived processes with a defined lifecycle. They run once per key and are ideal for orchestrating multi-step operations, which require external interaction via signals and queries. ```python {"CODE_LOAD::python/src/develop/my_workflow.py"} theme={null} import restate my_workflow = restate.Workflow("MyWorkflow") @my_workflow.main() async def run(ctx: restate.WorkflowContext, req: str) -> str: # ... implement workflow logic here --- return "success" @my_workflow.handler() async def interact_with_workflow(ctx: restate.WorkflowSharedContext, req: str): # ... implement interaction logic here ... return app = restate.app([my_workflow]) ``` * Initialize a `restate.Workflow` and specify its name (here `MyWorkflow`). * Every workflow **must** include a `run` handler: * This is the main orchestration entry point and is annotated with `@my_workflow.main()`. * It runs exactly once per workflow execution and uses the `WorkflowContext` * Resubmission of the same workflow will fail with "Previously accepted". The invocation ID can be found in the request header `x-restate-id`. * Use `ctx.key()` to access the workflow's unique ID * Additional handlers are annotated with `@my_workflow.handler()`. They must use the `WorkflowSharedContext` and can signal or query the workflow. They can run concurrently with the `run` handler and until the retention time expires. ## Configuring services Check out the [service configuration docs](/services/configuration) to learn how to configure service behavior, including timeouts and retention policies. # Serving Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/serving Create an endpoint to serve your services. Restate services can run in a few ways: as an HTTP handler, or as an AWS Lambda handler. ## Creating the app Create the app and bind one or multiple services to it: ```python {"CODE_LOAD::python/src/develop/serving.py#endpoint"} theme={null} import restate app = restate.app(services=[my_service, my_object]) ``` If you are using [FastAPI](https://fastapi.tiangolo.com/), you can mount the Restate app on a route as follows: ```python {"CODE_LOAD::python/src/develop/serving.py#fastapi"} theme={null} from fastapi import FastAPI app = FastAPI() app.mount("/restate/v1", restate.app(services=[my_service, my_object])) ``` Then, [register the service](/services/versioning#registering-a-deployment) with Restate specifying this route, here `localhost:9080/restate/v1`. ## Serving the app The Python SDK follows the [ASGI](https://asgi.readthedocs.io/en/latest/introduction.html) standard for the serving of the services. [Hypercorn](https://pypi.org/project/Hypercorn/) and [Uvicorn](https://www.uvicorn.org/) are two popular ASGI servers that you can use to serve your Restate services. ### Hypercorn The templates and examples use [Hypercorn](https://pypi.org/project/Hypercorn/) to serve the services. #### Hypercorn development server To use Hypercorn during development, you can run the app with: ```python {"CODE_LOAD::python/src/develop/serving.py#hypercorn"} theme={null} if __name__ == "__main__": import hypercorn import hypercorn.asyncio import asyncio conf = hypercorn.Config() conf.bind = ["0.0.0.0:9080"] asyncio.run(hypercorn.asyncio.serve(app, conf)) ``` Check out the [Quickstart](/quickstart) for more instructions. #### Hypercorn production server To run the app in production, we recommend using a configuration file for Hypercorn: ```toml hypercorn-config.toml theme={null} bind = "0.0.0.0:9080" h2_max_concurrent_streams = 2147483647 keep_alive_max_requests = 2147483647 keep_alive_timeout = 2147483647 workers = 8 ``` Run the app with: ```shell theme={null} python -m hypercorn --config hypercorn-config.toml example:app ``` This serves the app you specified in an `example.py` file, which contains [the `app`](/develop/python/serving#creating-the-app). ### Uvicorn You can also use [Uvicorn](https://www.uvicorn.org/) to serve [the app](/develop/python/serving#creating-the-app). To run the app with Uvicorn: ```shell theme={null} uvicorn example:app ``` Uvicorn does not support HTTP/2, so you need to tell Restate to use HTTP/1.1 when [registering the deployment](/services/versioning#deployments-supporting-only-http1-1). ## Validating request identity SDKs can validate that incoming requests come from a particular Restate instance. You can find out more about request identity in the [Security docs](/services/security#locking-down-service-access) ```python {"CODE_LOAD::python/src/develop/serving.py#identity"} theme={null} app = restate.app( services=[my_service], identity_keys=["publickeyv1_w7YHemBctH5Ck2nQRQ47iBBqhNHy4FV7t2Usbye2A6f"], ) ``` # State Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/state Store key-value state in Restate. Restate lets you persist key-value (K/V) state using its embedded K/V store. ## Key characteristics [Learn about Restate's embedded K/V store](/foundations/key-concepts#consistent-state). **State is only available for Virtual Objects and Workflows.** Scope & retention: * For Virtual Objects: State is scoped per object key and retained indefinitely. It is persisted and shared across all invocations for that object until explicitly cleared. * For Workflows: State is scoped per workflow execution (workflow ID) and retained only for the duration of the workflow’s configured retention time. Access Rules: * [Exclusive handlers](/foundations/handlers#handler-behavior) (e.g., `run()` in workflows) can read and write state. * [Shared handlers](/foundations/handlers#handler-behavior) can only read state and cannot mutate it. You can inspect and edit the K/V state via the UI and the [CLI](/services/introspection#inspecting-application-state). ## List all state keys To retrieve all keys for which the current Virtual Object has stored state: ```python {"CODE_LOAD::python/src/develop/state.py#statekeys"} theme={null} state_keys = ctx.state_keys() ``` ## Get state value To read a value by key: ```python {"CODE_LOAD::python/src/develop/state.py#get"} theme={null} my_string = await ctx.get("my-string-key", type_hint=str) or "default-key" my_number = await ctx.get("my-number-key", type_hint=int) or 123 ``` The return value is `None` if no value was stored. See the [serialization docs](/develop/python/serialization) to customize the state serializer. ## Set state value To write or update a value: ```python {"CODE_LOAD::python/src/develop/state.py#set"} theme={null} ctx.set("my-key", "my-new-value") ``` ## Clear state key To delete a specific key: ```python {"CODE_LOAD::python/src/develop/state.py#clear"} theme={null} ctx.clear("my-key") ``` ## Clear all state keys To remove all stored state for the current Virtual Object: ```python {"CODE_LOAD::python/src/develop/state.py#clear_all"} theme={null} ctx.clear_all() ``` ## Advanced: Eager vs. lazy state loading Restate supports two modes for loading state in handlers: ### Eager state (default) * **How it works**: State is automatically sent with the request when invoking a handler * **Benefits**: State is available immediately when the handler starts executing * **Behavior**: All reads and writes to state are local to the handler execution * **Best for**: Small to medium state objects that are frequently accessed ### Lazy state * **How it works**: State is fetched on-demand on `get` calls from the Restate Server * **Benefits**: Reduces initial request size and memory usage * **Setup**: Enable lazy state in the [service or handler configuration](/services/configuration) * **Best for**: Large state objects that aren't needed in every handler execution # Testing Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/python/testing Utilities to test your handler logic. The Python SDK has a testing harness to test your Restate handlers. This uses [Testcontainers](https://testcontainers.com/) to run a Restate Server in a Docker container and provides a client to let you test your Restate handlers. ## Setup Install the package: ```bash theme={null} pip install restate_sdk[harness] ``` ## Testing handlers If you have a service as follows: ```python {"CODE_LOAD::python/src/develop/testing.py#service"} theme={null} import restate greeter = restate.Service("greeter") @greeter.handler() async def greet(ctx: restate.Context, name: str) -> str: return f"Hello {name}!" app = restate.app(services=[greeter]) ``` Then you can test it via: ```python {"CODE_LOAD::python/src/develop/testing.py#testing"} theme={null} import restate with restate.test_harness(app) as harness: restate_client = harness.ingress_client() print(restate_client.post("/greeter/greet", json="Alice").json()) ``` # Rust SDK Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/rust # Concurrent Tasks Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/concurrent-tasks Execute multiple tasks concurrently and gather results. When building resilient applications, you often need to perform multiple operations in parallel to improve performance and user experience. Restate provides durable concurrency primitives that allow you to run tasks concurrently while maintaining deterministic execution during replays. ## When to use concurrent tasks Use concurrent tasks when you need to: * Call multiple external services simultaneously (e.g., fetching data from different APIs) * Race multiple operations and use the first result (e.g., trying multiple LLM providers) * Implement timeouts by racing an operation against a timer * Perform batch operations where individual tasks can run in parallel ## Key benefits * **Deterministic replay**: Restate logs the order of completion, ensuring consistent behavior during failures * **Fault tolerance**: If your handler fails, tasks that were already completed will be replayed with their results, while pending tasks will be retried ## Parallelizing tasks Start multiple durable operations concurrently by calling them without immediately awaiting: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#parallel"} theme={null} const call1 = ctx.run("fetch_user", async () => fetchUserData({ userId: 123 }) ); const call2 = ctx.run("fetch_orders", async () => fetchOrderHistory({ userId: 123 }) ); const call3 = ctx .serviceClient(analyticsService) .calculateMetrics({ userId: 123 }); const user = await call1; const orders = await call2; const metrics = await call3; ``` Check out the guide on [parallelizing work](guides/parallelizing-work). ## Retrieving results Restate provides several patterns for coordinating concurrent tasks. All patterns use `RestatePromise` combinators that log the order of completion, ensuring deterministic behavior during replays. ### Wait for all tasks to complete Similar to [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), waits for all promises to resolve successfully: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#all"} theme={null} const sleepPromise = ctx.sleep({ milliseconds: 100 }); const callPromise = ctx.serviceClient(myService).myHandler("Hi"); const externalCallPromise = ctx.run(() => httpCall()); const resultArray = await RestatePromise.all([ sleepPromise, callPromise, externalCallPromise, ]); ``` ### Wait for the first successful completion Similar to [`Promise.any`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/any), returns the first promise that resolves successfully: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#any"} theme={null} const sleepPromise1 = ctx.sleep({ milliseconds: 100 }); const sleepPromise2 = ctx.sleep({ milliseconds: 200 }); const callPromise = ctx.serviceClient(myService).myHandler("Hi"); const firstResult = await RestatePromise.any([ sleepPromise1, sleepPromise2, callPromise, ]); ``` ### Wait for the first to complete Similar to [`Promise.race`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race), returns the first promise to settle (resolve or reject): ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#race"} theme={null} const sleepPromise3 = ctx.sleep({ milliseconds: 100 }); const callPromise2 = ctx.serviceClient(myService).myHandler("Hi"); const firstToComplete = await RestatePromise.race([ sleepPromise3, callPromise2, ]); ``` ### Wait for all to settle Similar to [`Promise.allSettled`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled), waits for all promises to complete regardless of success or failure: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#allSettled"} theme={null} const sleepPromise4 = ctx.sleep({ milliseconds: 100 }); const callPromise3 = ctx.serviceClient(myService).myHandler("Hi"); const externalCallPromise2 = ctx.run(() => httpCall()); const allResults = await RestatePromise.allSettled([ sleepPromise4, callPromise3, externalCallPromise2, ]); ``` # Durable Steps Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/durable-steps Persist results of operations. Restate uses an execution log to replay operations after failures and suspensions. Non-deterministic operations (database calls, HTTP requests, UUID generation) must be wrapped to ensure deterministic replay. ## Run Use `ctx.run` to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate store its result in the execution log. ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#side_effect"} theme={null} const result = await ctx.run(async () => doDbRequest()); ``` Note that inside `ctx.run`, you cannot use the Restate context (e.g., `ctx.get`, `ctx.sleep`, or nested `ctx.run`). By default, results are serialized using [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON). See the [serialization docs](/develop/ts/serialization) to customize this. Failures in `ctx.run` are treated the same as any other handler error. Restate will retry it unless configured otherwise or unless a [`TerminalError`](/develop/ts/error-handling) is thrown. You can customize how `ctx.run` retries via: ```typescript {"CODE_LOAD::ts/src/develop/retries.ts#here"} theme={null} try { const myRunRetryPolicy = { initialRetryInterval: { milliseconds: 500 }, retryIntervalFactor: 2, maxRetryInterval: { seconds: 1 }, maxRetryAttempts: 5, maxRetryDuration: { seconds: 1 }, }; await ctx.run("write", () => writeToOtherSystem(), myRunRetryPolicy); } catch (e) { if (e instanceof restate.TerminalError) { // Undo or compensate here (see Sagas guide) } throw e; } ``` * You can limit retries by time or count * When the policy is exhausted, a `TerminalError` is thrown * See the [Error Handling Guide](/guides/error-handling) and the [Sagas Guide](/guides/sagas) for patterns like compensation If Restate doesn't receive new journal entries from a service for more than one minute (by default), it will automatically abort the invocation and retry it. However, some business logic can take longer to complete—for example, an LLM call that takes up to 3 minutes to respond. In such cases, you can adjust the service’s [abort timeout and inactivity timeout](/services/configuration) settings to accommodate longer execution times. For more information, see the [error handling guide](/guides/error-handling). ## Deterministic randoms The SDK provides deterministic helpers for random values — seeded by the invocation ID — so they return the **same result on retries**. ### UUIDs To generate stable UUIDs for things like idempotency keys: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#uuid"} theme={null} const uuid = ctx.rand.uuidv4(); ``` Do not use this in cryptographic contexts. ### Random numbers To generate a deterministic float between `0` and `1`: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#random_nb"} theme={null} const randomNumber = ctx.rand.random(); ``` This behaves like `Math.random()` but is deterministically replayable. ### Deterministic time To get the current millis since midnight, January 1, 1970, that is consistent across retries: ```typescript {"CODE_LOAD::ts/src/develop/journaling_results.ts#time"} theme={null} const now = await ctx.date.now(); ``` # Scheduling & Timers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/durable-timers Durable timers, scheduled actions, and sleep, backed by Restate. Restate provides durable, fault-tolerant timers that allow you to: * [Sleep](#durable-sleep): Pause a handler for a given duration * [Send delayed messages](#scheduling-async-tasks): Schedule handler invocations in the future * [Set timeouts](#timers-and-timeouts) for async operations * Implement patterns like [cron jobs](#cron-jobs) ## How it works Restate tracks and manages timers, ensuring they survive failures and restarts. For example, if a service is sleeping for 12 hours and fails after 8 hours, then Restate will make sure it only sleeps 4 hours more. If your handler runs on function-as-a-service platforms like AWS Lambda, Restate suspends the handler while it is sleeping, to free up resources. Since these platforms charge on execution time, this saves costs. ## Durable sleep To pause a handler for a set duration: ```typescript {"CODE_LOAD::ts/src/develop/durable_timers.ts#sleep"} theme={null} await ctx.sleep({ seconds: 10 }); ``` There is no hard limit on how long you can sleep. You can sleep for months or even years, but keep in mind: * If you sleep in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. * You need to keep the deployment version until the invocation completes (see [versioning](/services/versioning)). So for long sleeps, we recommend breaking this up into multiple handlers that call each other with [delayed messages](/develop/ts/service-communication#delayed-messages). The Restate SDK calculates the wake-up time based on the delay you specify. The Restate Server then uses this calculated time to wake up the handler. If the Restate Server and the SDK have different system clocks, the sleep duration might not be accurate. So make sure that the system clock of the Restate Server and the SDK have the same timezone and are synchronized. A mismatch can cause timers to fire earlier or later than expected. ## Scheduling async tasks To invoke a handler at a later time, use [delayed messages](/develop/ts/service-communication#delayed-messages). In theory, you can schedule future work in Restate in two ways: 1. **Delayed messages** (recommended) 2. **Sleep + send** - sleeping in the current handler, then sending a message At first sight, both approaches might seem to achieve similar results. However, we recommend to use delayed messages for the following reasons: * **Handler completes immediately**: The calling handler can finish execution and complete the invocation without waiting for the delay to finish * **No Virtual Object blocking**: Other requests to the same Virtual Object can be processed during the delay period * **Better for service versioning**: No long-running invocations that require keeping old service deployments around for a long time (see [service versioning](/services/versioning)) ## Timers and timeouts Most context actions are async actions that return a `RestatePromise`. `RestatePromise` supports setting timeouts, allowing you to bound how long your code waits for an operation. When an operation times out, it throws a `TimeoutError`. ```typescript {"CODE_LOAD::ts/src/develop/durable_timers.ts#timer"} theme={null} try { await ctx .serviceClient(myService) .myHandler("Hi") .orTimeout({ seconds: 5 }); } catch (error) { if (error instanceof restate.TimeoutError) { console.error("Operation timed out:", error); } else { throw error; // Re-throw other errors } } ``` For alternative ways of racing async operations, have a look at the [`RestatePromise` Combinator docs](/develop/ts/concurrent-tasks). ## Cron jobs Restate does not yet include native cron support, but you can implement your own cron scheduler using: * Durable timers * Virtual Objects * A repeat loop or sleep-schedule pattern Check out the guide on [implementing cron jobs](/guides/cron). # Error Handling Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/error-handling Stop infinite retries with Terminal Errors. Restate handles retries for failed invocations. Check out the [Error Handling guide](/guides/error-handling) to learn more about how Restate handles transient errors, terminal errors, retries, and timeouts. ## Retry strategies By default, Restate does infinite retries with an exponential backoff strategy. Check out the [error handling guide](/guides/error-handling) to learn how to customize this. ## Terminal Errors For failures for which you do not want retries, but instead want the invocation to end and the error message to be propagated back to the caller, you can throw a **terminal error**. You can throw a `TerminalError` with an optional HTTP status code and a message anywhere in your handler, as follows: ```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#terminal"} theme={null} throw new TerminalError("Something went wrong.", { errorCode: 500 }); ``` You can catch terminal errors, and build your control flow around it. When you throw a terminal error, you might need to undo the actions you did earlier in your handler to make sure that your system remains in a consistent state. Have a look at our [sagas guide](/guides/sagas) to learn more. ## Mapping errors to `TerminalError` If you're using external libraries (e.g., for validation), you might want to automatically convert certain error types into terminal errors. You can do this using the `asTerminalError` option in your [service configuration](/services/configuration). For example, to fail with `TerminalError` for each `MyValidationError`, do the following: ```typescript {"CODE_LOAD::ts/src/develop/error_handling.ts#as_terminal"} theme={null} class MyValidationError extends Error {} const greeter = restate.service({ name: "greeter", handlers: { greet: async (ctx: restate.Context, name: string) => { if (name.length === 0) { throw new MyValidationError("Length too short"); } return `Hello ${name}`; }, }, options: { asTerminalError: (err) => { if (err instanceof MyValidationError) { // My validation error is terminal return new restate.TerminalError(err.message, { errorCode: 400 }); } }, }, }); ``` # External Events Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/external-events Handle external events and human-in-the-loop patterns with durable waiting primitives. Sometimes your handlers need to pause and wait for external processes to complete. This is common in: * **Human-in-the-loop workflows** (approvals, reviews, manual steps) * **External system integration** (waiting for webhooks, async APIs) * **AI agent patterns** (tool execution, human oversight) This pattern is also known as the **callback** or **task token** pattern. ## Two Approaches Restate provides two primitives for handling external events: | Primitive | Use Case | Key Feature | | -------------------- | -------------------------- | ------------------------------------ | | **Awakeables** | Services & Virtual Objects | Unique ID-based completion | | **Durable Promises** | Workflows only | Named promises for simpler signaling | ## How it works Implementing this pattern in a distributed system is tricky, since you need to ensure that the handler can recover from failures and resume waiting for the external event. Restate makes promises are durable and distributed. They survive crashes and can be resolved or rejected by any handler in the workflow. To save costs on FaaS deployments, Restate lets the handler [suspend](/foundations/key-concepts#suspensions-on-faas) while awaiting the promise, and invokes it again when the result is available. ## Awakeables **Best for:** Services and Virtual Objects where you need to coordinate with external systems. ### Creating and waiting for awakeables 1. **Create an awakeable** - Get a unique ID and promise 2. **Send the ID externally** - Pass the awakeable ID to your external system 3. **Wait for result** - Your handler [suspends](/foundations/key-concepts#suspensions-on-faas) until the external system responds ```ts {"CODE_LOAD::ts/src/develop/awakeable.ts#here"} theme={null} // Create awakeable and get unique ID const { id, promise } = ctx.awakeable(); // Send ID to external system (email, queue, webhook, etc.) await ctx.run(() => requestHumanReview(name, id)); // Handler suspends here until external completion const review = await promise; ``` Both awakeables and durable promises use built-in [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) for (de)serialization. Complex objects are automatically serialized/deserialized. See the [serialization docs](/develop/ts/serialization) for customization options. Note that if you wait for an awakeable in an [exclusive handler](/foundations/handlers#handler-behavior) in a Virtual Object, all other calls to this object will be queued. ### Resolving/rejecting Awakeables External processes complete awakeables in two ways: * **Resolve** with success data → handler continues normally * **Reject** with error reason → throws a [terminal error](/develop/ts/error-handling) in the waiting handler #### Via SDK (from other handlers) **Resolve:** ```ts {"CODE_LOAD::../snippets/ts/src/develop/awakeable.ts#resolve"} theme={null} // Complete with success data ctx.resolveAwakeable(id, { approved: true, comments: "Looks good!" }); ``` **Reject:** ```ts {"CODE_LOAD::../snippets/ts/src/develop/awakeable.ts#reject"} theme={null} // Complete with error ctx.rejectAwakeable(id, "This cannot be reviewed."); ``` #### Via HTTP API External systems can complete awakeables using Restate's HTTP API: **Resolve with data:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/resolve \ --json '"Looks good!"' ``` **Reject with error:** ```shell theme={null} curl localhost:8080/restate/awakeables/sign_1PePOqp/reject \ -H 'content-type: text/plain' \ -d 'Review rejected: insufficient documentation' ``` ## Durable Promises **Best for:** Workflows where you need to signal between different workflow handlers. **Key differences from awakeables:** * No ID management - use logical names instead * Scoped to workflow execution lifetime Use this for: * Sending data to the run handler * Have handlers wait for events emitted by the run handler After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). The results of resolved Durable Promises remain available during this time. Update the retention time via the [service configuration](/services/configuration). ### Creating and waiting for promises Wait for a promise by name: ```ts {"CODE_LOAD::../snippets/ts/src/develop/awakeable.ts#promise"} theme={null} const review = await ctx.promise("review"); ``` ### Resolving/rejecting promises Resolve/reject from any workflow handler: ```ts {"CODE_LOAD::../snippets/ts/src/develop/awakeable.ts#resolve_promise"} theme={null} await ctx.promise("review").resolve(review); ``` ### Complete workflow example ```ts expandable {"CODE_LOAD::../snippets/ts/src/develop/awakeable.ts#review"} theme={null} restate.workflow({ name: "reviewWorkflow", handlers: { // Main workflow execution run: async (ctx: WorkflowContext, documentId: string) => { // Send document for review await ctx.run(() => askReview(documentId)); // Wait for external review submission const review = await ctx.promise("review"); // Process the review result return processReview(documentId, review); }, // External endpoint to submit reviews submitReview: async ( ctx: restate.WorkflowSharedContext, review: string ) => { // Signal the waiting run handler await ctx.promise("review").resolve(review); }, }, }); ``` ### Two signaling patterns **External → Workflow** (shown above): External handlers signal the run handler * Use for human approvals, external API responses, manual interventions * External handlers call the handler which resolves the promise **Workflow → External**: Run handler signals other handlers waiting for workflow events * Use for step completion notifications, status updates, result broadcasting * Run handler resolves promises that external handlers are awaiting ## Best Practices * **Use awakeables** for services/objects coordinating with external systems * **Use durable promises** for workflow signaling * **Always handle rejections** to gracefully manage failures * **Include timeouts** for long-running external processes # Logging Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/logging Configure the log level of your services. The TypeScript SDK includes an internal logger to help you monitor and debug your services. ## Log Levels You can control the verbosity of logs using environment variables: * **Set the log level:**\ Use the `RESTATE_LOGGING` environment variable.\ Possible values: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. * **Default log level:** * `INFO` if `NODE_ENV=production` * `DEBUG` otherwise * **Verbose journal logging:**\ If you set `RESTATE_LOGGING=TRACE`, you can enable even more detailed journal logs with:\ `RESTATE_JOURNAL_LOGGING=TRACE` ## Console Logging By default, using the Node.js console logger will print log statements repeatedly during replays.\ **To avoid duplicate logs during replays, use the Restate context logger.** The context logger wraps the console and suppresses duplicate log statements during replays: ```typescript {"CODE_LOAD::ts/src/develop/logging.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; const service = restate.object({ name: "Greeter", handlers: { greet: async (ctx: restate.ObjectContext, name: string) => { ctx.console.info("This will not be printed again during replays"); ctx.console.debug("This will not be printed again during replays"); // Any other console logging method can be used }, }, }); ``` * The context logger uses the same log level filtering as described above. * Use `ctx.console` for all logging inside handlers to ensure clean, non-redundant logs. # Serialization Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/serialization Customize serialization for SDK actions. Restate sends data over the network for storing state, journaling actions, awakeables, etc. Therefore, Restate needs to serialize and deserialize the journal entries. ## Default serialization By default, Typescript SDK uses the built-in [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) support to perform (de)serialization. ## Zod schemas You can use [Zod](https://zod.dev/) to define the schema of your handler input/output. Make sure to install the extra dependency for zod integration: [`@restatedev/restate-sdk-zod`](https://www.npmjs.com/package/@restatedev/restate-sdk-zod). Then do the following: ```typescript {"CODE_LOAD::ts/src/develop/serialization.ts#zod"} theme={null} import * as restate from "@restatedev/restate-sdk"; import { z } from "zod"; import { serde } from "@restatedev/restate-sdk-zod"; const Greeting = z.object({ name: z.string(), }); const GreetingResponse = z.object({ result: z.string(), }); const greeter = restate.service({ name: "Greeter", handlers: { greet: restate.handlers.handler( { input: serde.zod(Greeting), output: serde.zod(GreetingResponse) }, async (ctx: restate.Context, { name }) => { return { result: `You said hi to ${name}!` }; } ), }, }); ``` ## Custom serialization It is possible to implement customized serialization using the `Serde` interface. For example, to implement custom serializers for the handler input and output: ```typescript {"CODE_LOAD::ts/src/develop/serialization.ts#service_definition"} theme={null} const myService = restate.service({ name: "MyService", handlers: { myHandler: restate.handlers.handler( { // Set the input serde here input: restate.serde.binary, // Set the output serde here output: restate.serde.binary, }, async (ctx: Context, data: Uint8Array): Promise => { // Process the request return data; } ), }, }); ``` When sending a request to a handler configured with custom serde(s) you always need to manually specify them, because the client does not automatically infer what serde(s) should be used. To customize the serde to use on requests: ```typescript {"CODE_LOAD::ts/src/develop/serialization.ts#client"} theme={null} ctx.serviceClient(myService).myHandler( input, restate.rpc.opts({ input: restate.serde.binary, output: restate.serde.binary, }) ); ``` To customize the serde for other actions on the context: ```typescript {"CODE_LOAD::ts/src/develop/serialization.ts#actions"} theme={null} ctx.get("my-binary-data", restate.serde.binary); ctx.set("my-binary-data", new Uint8Array(), restate.serde.binary); ctx.awakeable(restate.serde.binary); await ctx.run("my-side-effect", () => new Uint8Array(), { serde: restate.serde.binary, }); ``` # Service Communication Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/service-communication Call other services from your handler. Your Restate handler can call other handlers in three ways: * **[Request-response calls](#request-response-calls)**: Call and wait for a response * **[One-way messages](#sending-messages)**: Send a message and continue * **[Delayed messages](#delayed-messages)**: Send a message after a delay To call a service from an external application, see the [HTTP](/services/invocation/http), [Kafka](/services/invocation/kafka), or [SDK Clients](/services/invocation/clients/typescript-sdk) documentation. [Learn how Restate how it works](/foundations/key-concepts#resilient-communication) ## Request-response calls To call a Restate handler and wait for its result: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#request_response"} theme={null} // To call a Service: const svcResponse = await ctx.serviceClient(myService).myHandler("Hi"); // To call a Virtual Object: const objResponse = await ctx .objectClient(myObject, "Mary") .myHandler("Hi"); // To call a Workflow: // `run` handler — can only be called once per workflow ID const wfResponse = await ctx .workflowClient(myWorkflow, "wf-id") .run("Hi"); // Other handlers can be called anytime within workflow retention const result = await ctx .workflowClient(myWorkflow, "wf-id") .interactWithWorkflow(); ``` Use generic calls when you don't have the service definition or need dynamic service names: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#generic_call"} theme={null} const response = await ctx.genericCall({ service: "MyObject", method: "myHandler", parameter: "Hi", key: "Mary", // drop this for Service calls inputSerde: restate.serde.json, outputSerde: restate.serde.json, }); ``` After a workflow's run handler completes, other handlers can still be called for up to 24 hours (default). Update this via the [service configuration](/services/configuration). Request-response calls between [exclusive handlers](/foundations/handlers#handler-behavior) of Virtual Objects may lead to deadlocks: * Cross deadlock: A → B and B → A (same keys). * Cycle deadlock: A → B → C → A. Use the UI or CLI to [cancel](/services/invocation/managing-invocations#cancel) and unblock deadlocked invocations. ## Sending messages To send a message to another Restate handler without waiting for a response: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#one_way"} theme={null} // To message a Service: ctx.serviceSendClient(myService).myHandler("Hi"); // To message a Virtual Object: ctx.objectSendClient(myObject, "Mary").myHandler("Hi"); // To message a Workflow: // `run` handler — can only be called once per workflow ID ctx.workflowSendClient(myWorkflow, "wf-id").run("Hi"); // Other handlers can be called anytime within workflow retention ctx.workflowSendClient(myWorkflow, "wf-id").interactWithWorkflow(); ``` Restate handles message delivery and retries, so the handler can complete and return without waiting for the message to be processed. Use generic send when you don't have the service definition: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#generic_send"} theme={null} ctx.genericSend({ service: "MyService", method: "myHandler", parameter: "Hi", inputSerde: restate.serde.json, }); ``` Calls to a Virtual Object execute in order of arrival, serially. Example: ```typescript {"CODE_LOAD::ts/src/develop/service_communication.ts#ordering"} theme={null} ctx.objectSendClient(myObject, "Mary").myHandler("I'm call A"); ctx.objectSendClient(myObject, "Mary").myHandler("I'm call B"); ``` Call A is guaranteed to execute before B. However, other invocations may interleave between A and B. ## Delayed messages To send a message after a delay: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#delayed"} theme={null} // To message a Service with a delay: ctx .serviceSendClient(myService) .myHandler("Hi", restate.rpc.sendOpts({ delay: { hours: 5 } })); // To message a Virtual Object with a delay: ctx .objectSendClient(myObject, "Mary") .myHandler("Hi", restate.rpc.sendOpts({ delay: { hours: 5 } })); // To message a Workflow with a delay: ctx .workflowSendClient(myWorkflow, "Mary") .run("Hi", restate.rpc.sendOpts({ delay: { hours: 5 } })); ``` Use generic send with a delay when you don't have the service definition: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#generic_delayed"} theme={null} ctx.genericSend({ service: "MyService", method: "myHandler", parameter: "Hi", inputSerde: restate.serde.json, delay: { seconds: 5 }, }); ``` Learn [how this is different](/develop/ts/durable-timers#scheduling-async-tasks) from sleeping and then sending a message. ## Using an idempotency key To prevent duplicate executions of the same call, add an idempotency key: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#idempotency_key"} theme={null} // For request-response const response = await ctx.serviceClient(myService).myHandler( "Hi", restate.rpc.opts({ idempotencyKey: "my-idempotency-key", }) ); // For sending a message ctx.serviceSendClient(myService).myHandler( "Hi", restate.rpc.sendOpts({ idempotencyKey: "my-idempotency-key", }) ); ``` Restate automatically deduplicates calls made during the same handler execution, so there's no need to provide an idempotency key in that case. However, if multiple handlers might call the same service independently, you can use an idempotency key to ensure deduplication across those calls. ## Attach to an invocation To wait for or get the result of a previously sent message: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#attach"} theme={null} const handle = ctx.serviceSendClient(myService).myHandler( "Hi", restate.rpc.sendOpts({ idempotencyKey: "my-idempotency-key", }) ); const invocationId = await handle.invocationId; // Later... const response = ctx.attach(invocationId); ``` * With an idempotency key: Wait for completion and retrieve the result. * Without an idempotency key: Can only wait, not retrieve the result. ## Cancel an invocation To cancel a running handler: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#cancel"} theme={null} const handle = ctx.serviceSendClient(myService).myHandler("Hi"); const invocationId = await handle.invocationId; // Cancel the invocation ctx.cancel(invocationId); ``` ## Advanced: sharing service type definitions When calling services, you need access to their type definitions for type safety. Here are four approaches to share service definitions without exposing implementation details: Export only the service type information from where you define your service: ```typescript {"CODE_LOAD::ts/src/develop/my_service.ts#api_export"} theme={null} export const myService = restate.service({ name: "MyService", handlers: { myHandler: async (ctx: restate.Context, greeting: string) => { return `${greeting}!`; }, }, }); export type MyService = typeof myService; ``` Import and use this definition in calling handlers: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#export_definition"} theme={null} const response = await ctx .serviceClient({ name: "MyService" }) .myHandler("Hi"); ``` Create a TypeScript interface matching the service's handler signatures. Useful when the service is in a different language or when you can't import the type definition: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#interface"} theme={null} interface MyService { myHandler(ctx: unknown, greeting: string): Promise; } const response = await ctx .serviceClient({ name: "MyService" }) .myHandler("Hi"); ``` When your service is published as a separate package, import it as a dev dependency to access its types: ```ts {"CODE_LOAD::ts/src/develop/service_communication.ts#dev_dependency"} theme={null} // import type { MyService } from "my-service-package"; const response = await ctx .serviceClient({ name: "MyService" }) .myHandler("Hi"); ``` ## See also * **[SDK Clients](/develop/ts/service-communication)**: Call Restate services from external applications * **[Error Handling](/develop/ts/error-handling)**: Handle failures and terminal errors in service calls * **[Durable Timers](/develop/ts/durable-timers)**: Implement timeouts for your service calls * **[Serialization](/develop/ts/serialization)**: Customize how data is serialized between services * **[Sagas](/guides/sagas)**: Roll back or compensate for canceled service calls. # Services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/services Implementing Restate services with the TypeScript SDK. The Restate TypeScript SDK is open source and available on [GitHub](https://github.com/restatedev/sdk-typescript). The Restate SDK lets you implement **handlers**. Handlers can be part of a **[Basic Service](/foundations/services#basic-service)**, a **[Virtual Object](/foundations/services#virtual-object)**, or a **[Workflow](/foundations/services#workflow)**. This page shows how to define them with the TypeScript SDK. ## Prerequisites * [NodeJS](https://nodejs.org/en/) >= v20 or [Bun](https://bun.sh/docs/installation) or [Deno](https://deno.land/#installation) * [npm CLI](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) >= 9.6.7 ## Getting started Get started quickly with the [TypeScript Quickstart](/quickstart). Add the [`@restatedev/restate-sdk`](https://www.npmjs.com/package/@restatedev/restate-sdk) dependency to your project to start developing Restate services. ## Basic Services [Basic Services](/foundations/services) group related **handlers** and expose them as callable endpoints: ```ts {"CODE_LOAD::ts/src/develop/service.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; export const myService = restate.service({ name: "MyService", handlers: { myHandler: async (ctx: restate.Context, greeting: string) => { return `${greeting}!`; }, }, }); restate.serve({ services: [myService] }); ``` * Define a service using [`restate.service`](https://restatedev.github.io/sdk-typescript/functions/_restatedev_restate-sdk.service). * The service has a name and a list of handlers. * Each handler has a name and can be called at `/myService/myHandler` * Handlers take the [`Context`](https://restatedev.github.io/sdk-typescript/interfaces/_restatedev_restate-sdk.Context) as the first argument. * Handlers can take one optional JSON-serializable input and must return a JSON-serializable output (see [custom serialization](/develop/ts/serialization) for advanced types). * Serve the service over HTTP (port `9080` by default). ## Virtual Objects [Virtual Objects](/foundations/services) are services that are stateful and key-addressable — each object instance has a unique ID and persistent state. ```ts {"CODE_LOAD::ts/src/develop/virtual_object.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; export const myObject = restate.object({ name: "MyObject", handlers: { myHandler: async (ctx: restate.ObjectContext, greeting: string) => { return `${greeting} ${ctx.key}!`; }, myConcurrentHandler: restate.handlers.object.shared( async (ctx: restate.ObjectSharedContext, greeting: string) => { return `${greeting} ${ctx.key}!`; } ), }, }); restate.serve({ services: [myObject] }); ``` * Define a Virtual Object using [`restate.object(...)`](https://restatedev.github.io/sdk-typescript/functions/_restatedev_restate-sdk.object) * Each instance is identified by a key (accessible via `ctx.key`). * Virtual Objects can have [exclusive and shared handlers](/foundations/handlers#handler-behavior). * Exclusive handlers receive an [`ObjectContext`](https://restatedev.github.io/sdk-typescript/interfaces/_restatedev_restate-sdk.ObjectContext), allowing read/write access to object state. * Shared handlers are wrapped in `handlers.object.shared(...)` and use the [`ObjectSharedContext`](https://restatedev.github.io/sdk-typescript/interfaces/_restatedev_restate-sdk.ObjectSharedContext) * Serve the Virtual Object over HTTP (port `9080` by default). ## Workflows [Workflows](/foundations/services) are long-lived processes with a defined lifecycle. They run once per key and are ideal for orchestrating multi-step operations, which require external interaction via signals and queries. ```ts {"CODE_LOAD::ts/src/develop/workflow.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; export const myWorkflow = restate.workflow({ name: "MyWorkflow", handlers: { run: async (ctx: restate.WorkflowContext, req: string) => { // implement workflow logic here return "success"; }, interactWithWorkflow: async (ctx: restate.WorkflowSharedContext) => { // implement interaction logic here // e.g. resolve a promise that the workflow is waiting on }, }, }); restate.serve({ services: [myWorkflow] }); ``` * Define a workflow with [`restate.workflow(...)`](https://restatedev.github.io/sdk-typescript/functions/_restatedev_restate-sdk.workflow) * Every workflow **must** include a `run` handler: * This is the main orchestration entry point * It runs exactly once per workflow execution and uses the [`WorkflowContext`](https://restatedev.github.io/sdk-typescript/interfaces/_restatedev_restate-sdk.WorkflowContext) * Resubmission of the same workflow will fail with "Previously accepted". The invocation ID can be found in the request header `x-restate-id`. * Use `ctx.key` to access the workflow's unique ID * Additional handlers must use the [`WorkflowSharedContext`](https://restatedev.github.io/sdk-typescript/interfaces/_restatedev_restate-sdk.WorkflowSharedContext) and can signal or query the workflow. They can run concurrently with the `run` handler and until the retention time expires. * Serve the Workflow over HTTP (port `9080` by default). ## Configuring services Check out the [service configuration docs](/services/configuration) to learn how to configure service behavior, including timeouts and retention policies. # Serving Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/serving Create an endpoint to serve your services. Restate services can run in a few ways: as a Node.js HTTP handler, as an AWS Lambda handler, or on other Javascript runtimes like Bun, Deno and Cloudflare Workers. ## Creating a Node.js HTTP handler Use `restate.serve` to serve the provided services, starting an HTTP/2 server on port `9080`. ```typescript {"CODE_LOAD::ts/src/develop/serving.ts#endpoint"} theme={null} import * as restate from "@restatedev/restate-sdk"; restate.serve({ services: [myService, myVirtualObject, myWorkflow], }); ``` If you need to manually control or customize the HTTP2 server, use `restate.createEndpointHandler` to create a Node HTTP/2 handler, and then use it to manually instantiate the HTTP server: ```ts {"CODE_LOAD::ts/src/develop/serving.ts#custom_endpoint"} theme={null} const http2Handler = restate.createEndpointHandler({ services: [myService, myVirtualObject, myWorkflow], }); const httpServer = http2.createServer(http2Handler); httpServer.listen(); ``` ## Creating a Lambda handler To register your service as a Lambda function, use the `/lambda` import component and use `restate.createEndpointHandler`: ```typescript {"CODE_LOAD::ts/src/develop/serving_lambda.ts#lambda"} theme={null} import * as restate from "@restatedev/restate-sdk/lambda"; export const handler = restate.createEndpointHandler({ services: [myService, myVirtualObject, myWorkflow], }); ``` The implementation of your services and handlers remains the same for both deployment options. Have a look at the [deployment section](/services/deploy/lambda) for guidance on how to deploy your services on AWS Lambda. ## Creating a Deno/Cloudflare Workers handler Other Javascript runtimes like Deno and Cloudflare Workers have built on top of the [Fetch Standard](https://github.com/whatwg/fetch) for defining HTTP server handlers. To register your service as a fetch handler, use the `/fetch` import component. ```typescript {"CODE_LOAD::ts/src/develop/serving_fetch.ts#fetch"} theme={null} import * as restate from "@restatedev/restate-sdk/fetch"; const handler = restate.createEndpointHandler({ services: [myService, myVirtualObject, myWorkflow], }); // Cloudflare expects the handler as a default export export default handler; // Deno expects to be passed the fetch function Deno.serve({ port: 9080 }, handler); ``` By default, a fetch handler will not advertise itself as working bidirectionally; the SDK will end the HTTP request at each suspension point, and the Restate runtime will re-invoke the service when there is more work to do. However, you can use the option `bidirectional: true` to change this on supported platforms, which will improve latencies once the service is re-registered with the runtime. * Deno (including Deno Deploy) supports HTTP2 and therefore bidirectional mode can be enabled. * Cloudflare Workers do not support end-to-end HTTP2 or bidirectional HTTP1.1, and enabling bidirectional mode will cause invocations to stall and time out. Services running on Workers must be discovered with the `--use-http1.1` CLI flag. Cloudflare Workers minification is not working correctly with the Restate SDK. If you see an issue similar to: ``` ✘ [ERROR] restate-cloudflare-worker: Uncaught TypeError: Cannot read properties of undefined (reading '__wbindgen_malloc') ``` Then most likely you have enabled minification when deploying. Disable it with `minify = false` in your `workers.toml` file. ## Validating request identity SDKs can validate that incoming requests come from a particular Restate instance. You can find out more about request identity in the [Security docs](/services/security#locking-down-service-access) ```typescript {"CODE_LOAD::ts/src/develop/serving.ts#identity"} theme={null} restate.serve({ services: [myService], identityKeys: ["publickeyv1_w7YHemBctH5Ck2nQRQ47iBBqhNHy4FV7t2Usbye2A6f"], }); ``` # State Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/state Store key-value state in Restate. Restate lets you persist key-value (K/V) state using its embedded K/V store. ## Key characteristics [Learn about Restate's embedded K/V store](/foundations/key-concepts#consistent-state). **State is only available for Virtual Objects and Workflows.** Scope & retention: * For Virtual Objects: State is scoped per object key and retained indefinitely. It is persisted and shared across all invocations for that object until explicitly cleared. * For Workflows: State is scoped per workflow execution (workflow ID) and retained only for the duration of the workflow’s configured retention time. Access Rules: * [Exclusive handlers](/foundations/handlers#handler-behavior) (e.g., `run()` in workflows) can read and write state. * [Shared handlers](/foundations/handlers#handler-behavior) can only read state and cannot mutate it. You can inspect and edit the K/V state via the UI and the [CLI](/services/introspection#inspecting-application-state). ## List all state keys To retrieve all keys for which the current Virtual Object has stored state: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#statekeys"} theme={null} const stateKeys = ctx.stateKeys(); ``` ## Get state value To read a value by key: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#get"} theme={null} const myString = (await ctx.get("my-string-key")) ?? "my-default"; const myNumber = (await ctx.get("my-number-key")) ?? 0; ``` Returns null if the key doesn't exist. See the [serialization docs](/develop/ts/serialization) to customize the state serializer. ## Set state value To write or update a value: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#set"} theme={null} ctx.set("my-key", "my-new-value"); ``` ## Clear state key To delete a specific key: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#clear"} theme={null} ctx.clear("my-key"); ``` ## Clear all state keys To remove all stored state for the current Virtual Object: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#clear_all"} theme={null} ctx.clearAll(); ``` ## Advanced: Eager vs. lazy state loading Restate supports two modes for loading state in handlers: ### Eager state (default) * **How it works**: State is automatically sent with the request when invoking a handler * **Benefits**: State is available immediately when the handler starts executing * **Behavior**: All reads and writes to state are local to the handler execution * **Best for**: Small to medium state objects that are frequently accessed ### Lazy state * **How it works**: State is fetched on-demand on `get` calls from the Restate Server * **Benefits**: Reduces initial request size and memory usage * **Setup**: Enable lazy state in the [service or handler configuration](/services/configuration) * **Best for**: Large state objects that aren't needed in every handler execution ## Advanced: Typed State You can specify the types of your Object's K/V state, to ensure type safety and better developer experience: ```typescript {"CODE_LOAD::ts/src/develop/state.ts#typed_state"} theme={null} type GreeterContext = restate.ObjectContext<{ name: string; count: number; }>; export const greeter = restate.object({ name: "Greeter", handlers: { greet: async (ctx: GreeterContext, name: string) => { const count: number = (await ctx.get("count")) ?? 0; ctx.set("name", name); }, }, }); ``` # Testing Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/develop/ts/testing Utilities to test your handler logic. The Typescript SDK has a companion library which makes it easy to test against a Restate container: [`@restatedev/restate-sdk-testcontainers`](https://www.npmjs.com/package/@restatedev/restate-sdk-testcontainers). This uses [Testcontainers](https://testcontainers.com/) to run a Restate Server in a Docker container and let you test your Restate handlers. ## Setup `RestateTestEnvironment.start` creates a Restate container and executes a user-provided closure to register services. An optional second argument allows you to specify a custom [Testcontainer](https://node.testcontainers.org/) for Restate. ```typescript {"CODE_LOAD::ts/src/develop/testing.test.ts#setup"} theme={null} describe("ExampleObject", () => { // import { RestateTestEnvironment } from "@restatedev/restate-sdk-testcontainers"; let restateTestEnvironment: RestateTestEnvironment; // import * as clients from "@restatedev/restate-sdk-clients"; let restateIngress: clients.Ingress; beforeAll(async () => { restateTestEnvironment = await RestateTestEnvironment.start({services: [router]}); restateIngress = clients.connect({ url: restateTestEnvironment.baseUrl() }); }, 20_000); afterAll(async () => { if (restateTestEnvironment !== undefined) { await restateTestEnvironment.stop(); } }); ``` ## Calling services The Restate ingress client can be used as usual (see the [clients documentation](/services/invocation/clients/typescript-sdk)) ```typescript {"CODE_LOAD::ts/src/develop/testing.test.ts#methods"} theme={null} it("Can call methods", async () => { const client = restateIngress.objectClient(router, "myKey"); await client.greet("Test!"); }); ``` ## Checking and mutating state The `stateOf` method on the `RestateTestEnvironment` class can be used to obtain a handle on the Virtual Object / Workflow state for a particular key. ```typescript {"CODE_LOAD::ts/src/develop/testing.test.ts#state"} theme={null} it("Can read state", async () => { const state = restateTestEnvironment.stateOf(router, "myKey"); expect(await state.getAll()).toStrictEqual({}); expect(await state.get("count")).toBeNull(); }); it("Can write state", async () => { const state = restateTestEnvironment.stateOf(router, "myKey"); await state.setAll({ count: 123, }); await state.set("count", 321); }); ``` ## Typed state `stateOf` can be provided with a type for the services state, to allow for type-safe state operations. ```typescript {"CODE_LOAD::ts/src/develop/testing.test.ts#typedstate"} theme={null} type ServiceState = { count: number }; it("Can operate on typed state", async () => { const state = restateTestEnvironment.stateOf(router, "myKey"); await state.setAll({ count: 1 }); await state.set("count", 2); }); ``` # Documentation Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/docs Build, deploy, and operate resilient applications with Restate Complete reference for building, deploying, and operating resilient applications with Restate. ## Build your services Choose your SDK and start building: ## Deploy and operate your services Deploy to Kubernetes, AWS Lambda, Vercel, Cloudflare Workers, or Deno Deploy Call services via HTTP, SDK clients, or Kafka events Manage service versions and compatibility Query system state and inspect running services ## Hosting Restate Choose between managed cloud or self-hosted deployment: **Managed platform** with instant setup, automatic scaling, and built-in monitoring. Perfect for getting started quickly without infrastructure management. **Full control** over your infrastructure with flexible deployment options. Single node, cluster, and Kubernetes deployment available. ## References System design • [Server config](/references/server-config) [TypeScript](https://restatedev.github.io/sdk-typescript) • [Java](https://restatedev.github.io/sdk-java/javadocs) • [Kotlin](https://restatedev.github.io/sdk-java/ktdocs) • [Go](https://pkg.go.dev/github.com/restatedev/sdk-go) ## New to Restate? Build your first service [AI agents](/use-cases/ai-agents) • [Microservices](/use-cases/microservice-orchestration) • [Workflows](/use-cases/workflows) • [Event Processing](/use-cases/event-processing) Core concepts and building blocks # Examples Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/examples # Actions Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/foundations/actions Essential context actions for building reliable handlers Context actions are methods available on the Restate Context object (`ctx`) that provide Restate's core capabilities. These actions enable durable execution, state management, service communication, and timing control. ### Durable steps Use `run` to safely wrap any non-deterministic operation, like HTTP calls or database responses, and have Restate persist its result. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#durable_steps"} theme={null} // External API call const apiResult = await ctx.run("fetch-data", async () => { const response = await fetch("https://api.example.com/data"); return response.json(); }); // Database operation const dbResult = await ctx.run("update-user", () => { return updateUserDatabase(userId, { name: "John" }); }); // Idempotency key generation const id = ctx.rand.uuidv4(); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/Actions.java#durable_steps"} theme={null} // External API call String apiResult = ctx.run(String.class, () -> fetchData("https://api.example.com/data")); // Database operation boolean dbResult = ctx.run(Boolean.class, () -> updateUserDatabase(userId, user)); // Idempotency key generation String id = ctx.random().nextUUID().toString(); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#durable_steps"} theme={null} # External API call api_result = await ctx.run_typed( "fetch-data", fetch_url, url="https://api.example.com/data" ) # Database operation db_result = await ctx.run_typed( "update-user", update_user_database, id=user_id, data={"name": "John"} ) # Idempotency key generation id = ctx.uuid() ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#durable_steps"} theme={null} // External API call apiResult, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return fetchData("https://api.example.com/data") }) if err != nil { return err } // Database operation success, err := restate.Run(ctx, func(ctx restate.RunContext) (bool, error) { return updateUserDatabase(userId, user) }) if err != nil { return err } // Idempotency key generation id := restate.Rand(ctx).UUID().String() ``` Without `run()`, these operations would produce different results during replay, breaking deterministic recovery. ## State management Available in Virtual Object and Workflow handlers for persistent key-value storage. ### Get Retrieve stored state by key. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#state_get"} theme={null} // Get with type and default value const profile = await ctx.get("profile"); const count = (await ctx.get("count")) ?? 0; const cart = (await ctx.get("cart")) ?? []; ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/StateExample.java#state_get"} theme={null} // Get with type and default value UserProfile profile = ctx.get(PROFILE).orElse(null); int count = ctx.get(COUNT).orElse(0); ShoppingCart cart = ctx.get(CART).orElse(new ShoppingCart()); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#state_get"} theme={null} # Get with type and default value profile = await ctx.get("profile", type_hint=UserProfile) count = await ctx.get("count", type_hint=int) or 0 cart = await ctx.get("cart", type_hint=ShoppingCart) or ShoppingCart() ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#state_get"} theme={null} // Get with type and default value profile, err := restate.Get[UserProfile](ctx, "profile") if err != nil { return err } count, err := restate.Get[int](ctx, "count") if err != nil { return err } cart, err := restate.Get[ShoppingCart](ctx, "cart") if err != nil { return err } ``` ### Set Store state that persists across function invocations. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#state_set"} theme={null} // Store simple values ctx.set("lastLogin", request.date); ctx.set("count", count + 1); // Store complex objects ctx.set("profile", { name: "John Doe", email: "john@example.com", }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/StateExample.java#state_set"} theme={null} // Store simple values ctx.set(COUNT, count + 1); ctx.set(LAST_LOGIN, request.date()); // Store complex objects ctx.set(PROFILE, new UserProfile("John Doe", "john@example.com")); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#state_set"} theme={null} # Store simple values ctx.set("lastLogin", request["date"]) ctx.set("count", count + 1) # Store complex objects ctx.set("profile", UserProfile(name="John Doe", email="john@example.com")) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#state_set"} theme={null} // Store simple values restate.Set(ctx, "lastLogin", request.Date) restate.Set(ctx, "count", count+1) // Store complex objects restate.Set(ctx, "profile", UserProfile{ Name: "John Doe", Email: "john@example.com", }) ``` ### Clear State is retained indefinitely for Virtual Objects, or for the configured retention period for Workflows. To clear state: ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#state_clear"} theme={null} // Clear specific keys ctx.clear("shoppingCart"); ctx.clear("sessionToken"); // Clear all user data ctx.clearAll(); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/StateExample.java#state_clear"} theme={null} // Clear specific keys ctx.clear(StateKey.of("shoppingCart", ShoppingCart.class)); ctx.clear(StateKey.of("sessionToken", String.class)); // Clear all user data ctx.clearAll(); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#state_clear"} theme={null} # Clear specific keys ctx.clear("shoppingCart") ctx.clear("sessionToken") # Clear all user data ctx.clear_all() ``` ## Service communication ### Request-response calls Make request-response calls to other services. Your function waits for the result. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#service_calls"} theme={null} // Call another service const validation = await ctx .serviceClient(ValidationService) .validateOrder(order); // Call Virtual Object function const profile = await ctx.objectClient(UserAccount, userId).getProfile(); // Submit Workflow const result = await ctx .workflowClient(OrderWorkflow, orderId) .run(order); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/Actions.java#service_calls"} theme={null} // Call another service var validation = ValidationServiceClient.fromContext(ctx).validateOrder(req.order()).await(); // Call Virtual Object function var profile = UserAccountClient.fromContext(ctx, req.userId()).getProfile().await(); // Submit Workflow var result = OrderWorkflowClient.fromContext(ctx, req.orderId()).run(req.order()).await(); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#service_calls"} theme={null} # Call another service from validation_service import validate_order validation = await ctx.service_call(validate_order, order) # Call Virtual Object function from user_account import get_profile profile = await ctx.object_call(get_profile, key=user_id, arg=None) # Submit Workflow from order_workflow import run result = await ctx.workflow_call(run, key=order_id, arg=order) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#service_calls"} theme={null} // Call another service validation, err := restate.Service[bool](ctx, "ValidationService", "ValidateOrder").Request(order) if err != nil { return err } // Call Virtual Object function profile, err := restate.Object[UserProfile](ctx, "UserAccount", userId, "GetProfile").Request(restate.Void{}) if err != nil { return err } // Submit Workflow _, err = restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").Request(order) if err != nil { return err } ``` ### Sending messages Make one-way calls that don't return results. Your function continues immediately. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#sending_messages"} theme={null} // Fire-and-forget notification ctx .serviceSendClient(NotificationService) .sendEmail({ userId, message: "Welcome!" }); // Background analytics ctx .serviceSendClient(AnalyticsService) .recordEvent({ kind: "user_signup", userId }); // Cleanup task ctx.objectSendClient(ShoppingCartObject, userId).emtpyExpiredCart(); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/Actions.java#sending_messages"} theme={null} // Fire-and-forget notification NotificationServiceClient.fromContext(ctx) .send() .sendEmail(new EmailRequest(userId, "Welcome!")); // Background analytics AnalyticsServiceClient.fromContext(ctx).send().recordEvent(event); // Cleanup task ShoppingCartObjectClient.fromContext(ctx, userId).send().emptyExpiredCart(); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#sending_messages"} theme={null} # Fire-and-forget notification from notification_service import send_email ctx.service_send(send_email, {"userId": user_id, "message": "Welcome!"}) # Background analytics from analytics_service import record_event ctx.service_send(record_event, {"kind": "user_signup", "userId": user_id}) # Cleanup task from shopping_cart_object import empty_expired_cart ctx.object_send(empty_expired_cart, key=user_id, arg=None) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#sending_messages"} theme={null} // Fire-and-forget notification restate.ServiceSend(ctx, "NotificationService", "SendEmail").Send(message) // Background analytics restate.ServiceSend(ctx, "AnalyticsService", "RecordEvent").Send(event) // Cleanup task restate.ObjectSend(ctx, "ShoppingCartObject", userId, "EmptyExpiredCart").Send(restate.Void{}) ``` ### Delayed messages Schedule handlers to run in the future. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#delayed_messages"} theme={null} // Schedule reminder for tomorrow ctx.serviceSendClient(ReminderService).sendReminder( { userId, message }, sendOpts({ delay: { days: 1 }, }) ); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/Actions.java#delayed_messages"} theme={null} // Schedule reminder for tomorrow ReminderServiceClient.fromContext(ctx).send().sendReminder(reminderRequest, Duration.ofDays(1)); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#delayed_messages"} theme={null} # Schedule reminder for tomorrow from notification_service import send_reminder ctx.service_send( send_reminder, {"userId": user_id, "message": message}, send_delay=timedelta(days=1), ) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#delayed_messages"} theme={null} // Schedule reminder for tomorrow restate.ServiceSend(ctx, "ReminderService", "SendReminder").Send( message, restate.WithDelay(24*time.Hour), ) ``` ## Durable timers and timeouts Pause function execution for a specific duration. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#durable_timers"} theme={null} // Sleep for specific duration await ctx.sleep({ minutes: 5 }); // 5 minutes // Wait for action or timeout const result = await ctx .workflowClient(OrderWorkflow, orderId) .run(order) .orTimeout({ minutes: 5 }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/Actions.java#durable_timers"} theme={null} // Sleep for specific duration ctx.sleep(Duration.ofMinutes(5)); // 5 minutes // Wait for action or timeout try { OrderWorkflowClient.fromContext(ctx, req.orderId()) .run(req.order()) .await(Duration.ofMinutes(5)); } catch (TimeoutException e) { // Handle timeout } ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#durable_timers"} theme={null} # Sleep for specific duration await ctx.sleep(timedelta(minutes=5)) # 5 minutes # Wait for action or timeout from order_workflow import run match await restate.select( result=ctx.workflow_call(run, key=order_id, arg=order), timeout=ctx.sleep(timedelta(minutes=5)), ): case ["result", result]: return result case _: print("Order processing timed out") ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#durable_timers"} theme={null} // Sleep for specific duration if err := restate.Sleep(ctx, 5*time.Minute); err != nil { return err } // Wait for action or timeout sleepFuture := restate.After(ctx, 5*time.Minute) callFuture := restate.Workflow[restate.Void](ctx, "OrderWorkflow", orderId, "Run").RequestFuture(order) selector := restate.Select(ctx, sleepFuture, callFuture) switch selector.Select() { case sleepFuture: if err := sleepFuture.Done(); err != nil { return err } // Timeout occurred case callFuture: if _, err := callFuture.Response(); err != nil { return err } // Call completed } ``` Handlers consume no resources while sleeping and resume at exactly the right time, even across restarts ([see suspensions](/foundations/key-concepts#suspensions-on-faas)). ## Workflow events Use durable promises to wait for external events or human input in your workflows. Create promises that external systems can resolve to send data to your workflow. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#workflow_promises"} theme={null} // Wait for external event const paymentResult = await ctx.promise( "payment-completed" ); // Wait for human approval const approved = await ctx.promise("manager-approval"); // Wait for multiple events const [payment, inventory] = await RestatePromise.all([ ctx.promise("payment").get(), ctx.promise("inventory").get(), ]); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/WorkflowExample.java#workflow_promises"} theme={null} // Wait for external event PaymentResult paymentResult = ctx.promise(PAYMENT_COMPLETED).future().await(); // Wait for human approval Boolean approved = ctx.promise(MANAGER_APPROVAL).future().await(); // Wait for multiple events var paymentFuture = ctx.promise(PAYMENT).future(); var inventoryFuture = ctx.promise(INVENTORY).future(); PaymentResult payment = paymentFuture.await(); InventoryResult inventory = inventoryFuture.await(); ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#workflow_promises"} theme={null} # Wait for external event payment_result = await ctx.promise( "payment-completed", type_hint=PaymentResult ).value() # Wait for human approval approved = await ctx.promise("manager-approval", type_hint=bool).value() # Wait for multiple events using gather payment_promise = ctx.promise("payment", type_hint=PaymentResult) inventory_promise = ctx.promise("inventory", type_hint=InventoryConfirmation) payment, inventory = await restate.gather( payment_promise.value(), inventory_promise.value() ) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#workflow_promises"} theme={null} // Wait for external event paymentResult, err := restate.Promise[PaymentResult](ctx, "payment-completed").Result() if err != nil { return err } // Wait for human approval approved, err := restate.Promise[bool](ctx, "manager-approval").Result() if err != nil { return err } // Wait for multiple events paymentFuture := restate.Promise[PaymentResult](ctx, "payment") inventoryFuture := restate.Promise[InventoryResult](ctx, "inventory") payment, err := paymentFuture.Result() if err != nil { return err } inventory, err := inventoryFuture.Result() if err != nil { return err } ``` Resolve promises from signal handlers. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/actions/actions.ts#signal_functions"} theme={null} // In a signal function confirmPayment: async ( ctx: WorkflowSharedContext, result: PaymentResult ) => { await ctx.promise("payment-completed").resolve(result); }, // In a signal function approveRequest: async (ctx: WorkflowSharedContext, approved: boolean) => { await ctx.promise("manager-approval").resolve(approved); }, ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/actions/WorkflowExample.java#signal_functions"} theme={null} // In a signal function @Shared public void confirmPayment(SharedWorkflowContext ctx, PaymentResult result) { ctx.promiseHandle(PAYMENT_COMPLETED).resolve(result); } // In a signal function @Shared public void approveRequest(SharedWorkflowContext ctx, Boolean approved) { ctx.promiseHandle(MANAGER_APPROVAL).resolve(approved); } ``` ```python Python {"CODE_LOAD::python/src/foundations/actions/actions.py#signal_functions"} theme={null} # In a signal function @workflow_example_workflow.handler() async def confirm_payment(ctx: WorkflowSharedContext, result: PaymentResult) -> None: await ctx.promise("payment-completed").resolve(result) # In a signal function @workflow_example_workflow.handler() async def approve_request(ctx: WorkflowSharedContext, approved: bool) -> None: await ctx.promise("manager-approval").resolve(approved) ``` ```go Go {"CODE_LOAD::go/foundations/actions/actions.go#signal_functions"} theme={null} // In a signal function func (WorkflowExample) ConfirmPayment(ctx restate.WorkflowSharedContext, result PaymentResult) error { if err := restate.Promise[PaymentResult](ctx, "payment-completed").Resolve(result); err != nil { return err } return nil } // In a signal function func (WorkflowExample) ApproveRequest(ctx restate.WorkflowSharedContext, approved bool) error { if err := restate.Promise[bool](ctx, "manager-approval").Resolve(approved); err != nil { return err } return nil } ``` To implement a similar pattern in Basic Services or Virtual Objects, have a look at awakeables ([TS](/develop/ts/external-events)/[Java/Kotlin](/develop/java/external-events)/[Go](/develop/ts/external-events)/[Python](/develop/python/external-events)). # Handlers Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/foundations/handlers Learn how to create, invoke and manage handlers in Restate services Handlers are the core building blocks of Restate services. Each service has a list of handlers: durable functions that can be invoked over HTTP, Kafka, or using typed clients. ## Writing handlers Handlers receive a Restate context object (`ctx`) and a single, optional input: ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/functions/functions.ts#here"} theme={null} async function myHandler(ctx: restate.Context, input: MyInput) { const result = await ctx.run("process", () => processData(input)); return { success: true, result }; } ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/functions/Functions.java#here"} theme={null} @Handler public ProcessingResult myHandler(Context ctx, MyInput myInput) { return ctx.run(ProcessingResult.class, () -> processData(myInput)); } ``` ```python Python {"CODE_LOAD::python/src/foundations/functions/functions.py#here"} theme={null} async def my_handler(ctx: Context, req: MyInput) -> ProcessingResult: return await ctx.run_typed("process", process_data, req=req) ``` ```go Go {"CODE_LOAD::go/foundations/functions/functions.go#here"} theme={null} func MyHandler(ctx restate.Context, input MyInput) (ProcessingResult, error) { return restate.Run(ctx, func(ctx restate.RunContext) (ProcessingResult, error) { return processData(input) }) } ``` The input and output can be any JSON-serializable type. For other types, consult the serialization documentation of your SDK. ## Context types The context type you use depends on your service type: * **`Context`** - Basic Services (stateless handlers) * **`ObjectContext`** - Virtual Objects (exclusive handlers with state access) * **`ObjectSharedContext`** - Virtual Objects (concurrent read-only handlers) * **`WorkflowContext`** - Workflows (main run handler) * **`WorkflowSharedContext`** - Workflows (signal/query handlers) ## Handler behavior Virtual Objects and Workflows support two handler types: **Exclusive handlers** (`ObjectContext`, `WorkflowContext`) can read and write state but only one runs at a time per key to prevent conflicts. **Shared handlers** (`ObjectSharedContext`, `WorkflowSharedContext`) can only read state but run concurrently without blocking—useful for querying status during long-running operations. # Invocations Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/foundations/invocations Invoke handlers in Restate services over HTTP, Kafka or with typed clients. An invocation is a request to execute a handler that is part of a Restate service. There are three ways to invoke a handler: over HTTP, using typed clients, or through Kafka topics. ## HTTP invocations To call a function over HTTP, send a request to the Restate Server, specifying the service and the function you want to invoke. For example, to call the `processPayment` function of the `PaymentService`: ```bash theme={null} curl -X POST localhost:8080/PaymentService/processPayment \ --json '{"amount": 100, "currency": "USD"}' ``` The Restate Server ingress endpoint is here hosted at `localhost:8080`. To call Virtual Objects or Workflows, you need to specify the object key or workflow ID in the URL. For example, to call `updateBalance` of the `UserAccount` object with key `user123`: ```bash theme={null} curl -X POST localhost:8080/UserAccount/user123/updateBalance \ --json '{"amount": 50}' ``` Check out the [HTTP invocation](/services/invocation/http) docs to learn more. ## Typed client invocations Use typed clients from external applications to invoke Restate handlers: ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/invocations/client.ts#here"} theme={null} //import * as clients from "@restatedev/restate-sdk-clients"; const restateClient = clients.connect({ url: "http://localhost:8080" }); // To call a service: const greet = await restateClient .serviceClient(greeterService) .greet({ greeting: "Hi" }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/invocations/Client.java#here"} theme={null} var restate = Client.connect("http://localhost:8080"); // To call a service: MyServiceClient.fromClient(restate).myHandler(req); ``` ```go Go {"CODE_LOAD::go/foundations/invocations/client.go#here"} theme={null} restateClient := restateingress.NewClient("http://localhost:8080") // To call a service response, err := restateingress.Service[*MyInput, *MyOutput]( restateClient, "MyService", "MyHandler"). Request(context.Background(), &input) if err != nil { return err } ``` For service-to-service calls within handlers, see [Service Communication](/foundations/handlers#service-communication). ## Kafka invocations You can connect your Restate handlers to Kafka topics, to let Restate invoke them for each message. Consult the [Kafka Quickstart](/guides/kafka-quickstart) to get started. ## Idempotency Add an idempotency key to your request header to let Restate deduplicate retries: ```bash theme={null} curl -X POST localhost:8080/PaymentService/processPayment \ -H 'idempotency-key: payment-123' \ --json '{"amount": 100, "currency": "USD"}' ``` On retry, Restate returns the first invocation’s result or lets you attach to it if still running. ## Inspecting invocations Use the Restate UI to monitor and troubleshoot invocations: Durable Execution ## Attaching to invocations Attach to ongoing invocations to retrieve their results: ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/invocations/client.ts#attach"} theme={null} const handle = ctx .serviceSendClient(myService) .myHandler("Hi", sendOpts({ idempotencyKey: "my-key" })); const invocationId = await handle.invocationId; // Later... const response = ctx.attach(invocationId); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/invocations/Client.java#attach"} theme={null} var handle = MyServiceClient.fromContext(ctx) .send() .myHandler("Hi", opt -> opt.idempotencyKey("my-key")); var response = handle.attach().await(); ``` ```python Python {"CODE_LOAD::python/src/foundations/invocations/client.py#attach"} theme={null} handle = ctx.service_send(my_handler, "Hi", idempotency_key="my-key") invocation_id = await handle.invocation_id() # Later... await ctx.attach_invocation(invocation_id) ``` ```go Go {"CODE_LOAD::go/foundations/invocations/client.go#attach"} theme={null} // Execute the request and retrieve the invocation id invocationId := restate. ServiceSend(ctx, "MyService", "MyHandler"). Send("Hi", restate.WithIdempotencyKey("my-idempotency-key")). GetInvocationId() // Later re-attach to the request response, err := restate.AttachInvocation[string](ctx, invocationId).Response() ``` Or over HTTP: ```bash theme={null} curl localhost:8080/restate/invocation/inv_1234567890abcdef/attach ``` This is useful when another process needs the result of an ongoing operation or wants to check whether it has completed. ## Cancelling invocations Cancel running invocations when they're no longer needed: ```bash CLI theme={null} restate invocations cancel inv_1234567890abcdef ``` ```bash HTTP theme={null} curl -X PATCH http://localhost:9070/invocations/inv_1gdJBtdVEcM942bjcDmb1c1khoaJe11Hbz/cancel ``` Or programmatically: ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/invocations/client.ts#cancel"} theme={null} const handle = ctx.serviceSendClient(myService).myHandler("Hi"); const invocationId = await handle.invocationId; // Cancel the invocation ctx.cancel(invocationId); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/invocations/Client.java#cancel"} theme={null} var handle = MyServiceClient.fromContext(ctx).send().myHandler("Hi"); // Cancel the invocation handle.cancel(); ``` ```python Python {"CODE_LOAD::python/src/foundations/invocations/client.py#cancel"} theme={null} handle = ctx.service_send(my_handler, "Hi", idempotency_key="my-key") invocation_id = await handle.invocation_id() # Cancel the invocation ctx.cancel_invocation(invocation_id) ``` ```go Go {"CODE_LOAD::go/foundations/invocations/client.go#cancel"} theme={null} // Execute the request and retrieve the invocation id invocationId := restate. ServiceSend(ctx, "MyService", "MyHandler"). Send("Hi"). GetInvocationId() // I don't need this invocation anymore, let me just cancel it restate.CancelInvocation(ctx, invocationId) ``` To roll back the actions the handler already completed before cancellation, check out at the [sagas guide](/guides/sagas). # Key Concepts Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/foundations/key-concepts Core concepts of Restate applications This page explains the key concepts and architecture that make Restate applications reliable and resilient. ## Application Structure A Restate application consists of the following components: Application Structure ### Restate Services Your business logic lives in **services**: regular applications that embed the Restate SDK. Services contain **handlers** (durable functions) that process requests and execute business logic. * **Deploy anywhere**: Services run in your infrastructure as containers, serverless functions, VMs, or Kubernetes pods * **Familiar programming model**: Services look and feel like normal applications with added reliability by using the Restate SDK. Restate has SDKs for TypeScript, Java, Kotlin, Python, Go, and Rust. ### Restate Server The **Restate Server** sits in front of your services, similar to a reverse proxy or message broker. It handles incoming requests, manages service discovery, and provides durable execution capabilities. The server is a single binary written in Rust with a stream-processing architecture for **low latency and high throughput**. It can be deployed in a high-availability cluster or as a single-node instance. Look at the options for Restate Cloud for a fully managed Restate deployment. ### Invocations An **invocation** represents a request to execute a handler. Restate tracks each invocation through completion, ensuring it runs exactly once regardless of failures. You can invoke Restate handlers over HTTP, Kafka, or programmatically. ## Durable Execution Restate's core feature is **durable execution**: the ability to make any code execution reliable and fault-tolerant without changing how you write business logic. ### How it works Restate tracks every step of your code execution in a **journal**. When you call other services, update databases, set timers, or perform any side-effecting operation, Restate records both the operation and its result. If your function crashes or fails, Restate replays the journal, skipping completed steps and resuming from exactly where it left off. Durable Execution In this example, if sending the receipt fails, Restate automatically retries the function but skips the payment processing since it already completed successfully. Each step is recorded in the journal and won't repeat on retry. For a detailed technical walkthrough of the lifecycle of a request, read this **[guide](/guides/request-lifecycle)**. ### What it covers Restate's implementation of Durable Execution protects your applications from common failure scenarios: * **Service crashes or restarts**: Any ongoing executions are retried on new instances. * **Restate Server crashes or restarts**: The Restate Server can be deployed as a high-availability cluster, so if a node fails, others can take over without losing any execution state. If Restate is deployed as a single node, it can still recover from crashes by recovering from persistent disk. If a handler calls an API that times out, Restate will retry the run action with exponential backoff. You can tune the retry policy through the [service configuration](/services/configuration#retries). If you add an idempotency key to your request headers, Restate will automatically ensure that requests are deduplicated. Duplicate requests will return the same result as the original request. * **Between Server and service**: If the service cannot send journal entries to the Restate Server, it will stop the handler execution. The Restate Server will retry the execution on another service instance. * **Between nodes in a Restate cluster**: Restate clusters rely on a consensus algorithm to accept new entries in the log. The consensus algorithm requires a majority of nodes to be available, so if a network partition occurs, the cluster will still be able to process requests as long as a majority can still communicate. ## Resilient Communication Beyond executing individual handlers reliably, Restate also ensures that communication between services is resilient. communication Restate proxies all communication towards and between services. It provides a **resilient RPC framework**: * Automatic retries and recovery: Failed calls are retried until they succeed * No duplicates: Same call won’t execute twice * Full observability: See all calls and call chains in the UI * No message queues needed: Restate handles message delivery ## Consistent State The Restate Server includes an embedded **key-value store** for persisting application state in Virtual Objects and Workflows. Virtual Objects Key characteristics: * **Usage**: Implement consistent state machines, session state, or agent context and memory. * **Scoped per entity**: Each Virtual Object and Workflow execution has its own isolated state. * **Automatically journaled**: State updates are recorded alongside execution steps for consistency. State is never out of sync with the execution. * **Single-writer guarantee**: Only one handler can modify state at a time, preventing race conditions. * **Stateless services**: The Restate Server stores all state and execution history, delivering them with each request. Your services remain stateless, can scale horizontally, and run stateful logic even on serverless platforms. Learn more about when to store state in Restate vs. in a database with [this guide](/guides/databases). ## Suspensions on FaaS When running handlers on function-as-a-service platforms (FaaS), like AWS Lambda, Restate can suspend functions while they wait for external events (like user input or long-running tasks). For example, if a handler needs to wait for a user to approve an order, Restate can suspend the function until the user responds. This lets you run long-running workflows on FaaS platforms **without paying for wait time**. ## Next Steps Now that you understand the core concepts, dive deeper into how to build with Restate: * **[Services](/foundations/services)**: Understand the three service types and when to use each * **[Handlers](/foundations/handlers)**: Learn how to write handlers that take advantage of durable execution * **[Actions](/foundations/actions)**: Master the context helpers for state management, service calls, and timing control # Services Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/foundations/services Understanding Restate's three service types and when to use each Restate provides three service types optimized for different use cases. ## Service types comparison | | **Basic Service** | **Virtual Object** | **Workflow** | | ---------------- | -------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------- | | **What** | Independent stateless handlers | Stateful entity with a unique key | Multi-step processes that execute exactly-once per ID | | **State** | None | Isolated per object key | Isolated per workflow instance | | **Concurrency** | Unlimited parallel execution | Single writer per key (+ concurrent readers) | Single `run` handler per ID (+ concurrent signals/queries) | | **Key Features** | Durable execution, service calls | Built-in K/V state, single-writer consistency | Durable promises, signals, lifecycle management | | **Best For** | ETL, sagas, parallelization, background jobs | User accounts, shopping carts, agents, state machines, stateful event processing | Approvals, onboarding workflows, multi-step flows | ## Basic Service Basic Services group related handlers as callable endpoints. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/services/basic_service.ts#here"} theme={null} 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) ); } }, }, }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/services/SubscriptionService.java#here"} theme={null} @Service public class SubscriptionService { @Handler public void add(Context ctx, SubscriptionRequest req) { var paymentId = ctx.random().nextUUID().toString(); String payRef = ctx.run("pay", String.class, () -> createRecurringPayment(req.creditCard(), paymentId)); for (String subscription : req.subscriptions()) { ctx.run("add-" + subscription, () -> createSubscription(req.userId(), subscription, payRef)); } } } ``` ```python Python {"CODE_LOAD::python/src/foundations/services/basic_service.py#here"} theme={null} subscription_service = restate.Service("SubscriptionService") @subscription_service.handler() async def add(ctx: Context, req: SubscriptionRequest) -> None: payment_id = str(uuid.uuid4()) pay_ref = await ctx.run_typed( "pay", create_recurring_payment, credit_card=req.credit_card, payment_id=payment_id, ) for subscription in req.subscriptions: await ctx.run_typed( "add-" + subscription, create_subscription, user_id=req.user_id, subscription=subscription, pay_ref=pay_ref, ) ``` ```go Go {"CODE_LOAD::go/foundations/services/basic_service.go#here"} theme={null} type SubscriptionService struct{} func (SubscriptionService) Add(ctx restate.Context, req SubscriptionRequest) error { paymentId := restate.Rand(ctx).UUID().String() payRef, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return createRecurringPayment(req.CreditCard, paymentId) }) if err != nil { return err } for _, subscription := range req.Subscriptions { _, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return createSubscription(req.UserId, subscription, payRef) }) if err != nil { return err } } return nil } ``` **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 ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/services/object.ts#here"} theme={null} const cartObject = restate.object({ name: "ShoppingCart", handlers: { addItem: async (ctx: restate.ObjectContext, item: Item) => { const items = (await ctx.get("cart")) ?? []; items.push(item); ctx.set("cart", items); return items; }, getTotal: restate.handlers.object.shared( async (ctx: restate.ObjectSharedContext) => { const items = (await ctx.get("cart")) ?? []; return items.reduce((sum, item) => sum + item.price * item.quantity, 0); } ), }, }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/services/ShoppingCartObject.java#here"} theme={null} @VirtualObject public class ShoppingCartObject { private static final StateKey CART = StateKey.of("cart", Cart.class); @Handler public Cart addItem(ObjectContext ctx, Item item) { var cart = ctx.get(CART).orElse(new Cart()); var newCart = cart.addItem(item); ctx.set(CART, newCart); return cart; } @Shared public double getTotal(SharedObjectContext ctx) { var cart = ctx.get(CART).orElse(new Cart()); return cart.getTotalPrice(); } } ``` ```python Python {"CODE_LOAD::python/src/foundations/services/object.py#here"} theme={null} cart_object = restate.VirtualObject("ShoppingCart") @cart_object.handler() async def add_item(ctx: ObjectContext, item: Item) -> Cart: cart = await ctx.get("cart", type_hint=Cart) or Cart() cart.items.append(item) ctx.set("cart", cart) return cart @cart_object.handler(kind="shared") async def get_total(ctx: ObjectSharedContext) -> float: cart = await ctx.get("cart", type_hint=Cart) or Cart() return sum(item.price * item.quantity for item in cart.items) ``` ```go Go {"CODE_LOAD::go/foundations/services/object.go#here"} theme={null} type ShoppingCartObject struct{} func (ShoppingCartObject) AddItem(ctx restate.ObjectContext, item Item) ([]Item, error) { items, err := restate.Get[[]Item](ctx, "items") if err != nil { return nil, err } items = append(items, item) restate.Set(ctx, "items", items) return items, nil } func (ShoppingCartObject) GetTotal(ctx restate.ObjectSharedContext) (float64, error) { items, err := restate.Get[[]Item](ctx, "items") if err != nil { return 0, err } total := 0.0 for _, item := range items { total += item.Price * float64(item.Quantity) } return total, nil } ``` **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. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/services/workflow.ts#here"} theme={null} 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("email-link-clicked"); return clickSecret === secret; }, click: async ( ctx: restate.WorkflowSharedContext, request: { secret: string } ) => { await ctx.promise("email-link-clicked").resolve(request.secret); }, }, }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/services/SignupWorkflow.java#here"} theme={null} @Workflow public class SignupWorkflow { private static final DurablePromiseKey EMAIL_LINK_CLICKED = DurablePromiseKey.of("email-link-clicked", String.class); @Workflow public boolean run(WorkflowContext ctx, User user) { // workflow ID = user ID; workflow runs once per user String userId = ctx.key(); ctx.run(() -> createUserEntry(userId, user)); String secret = ctx.random().nextUUID().toString(); ctx.run(() -> sendVerificationEmail(user, secret)); String clickSecret = ctx.promise(EMAIL_LINK_CLICKED).future().await(); return clickSecret.equals(secret); } @Shared public void click(SharedWorkflowContext ctx, ClickRequest request) { ctx.promiseHandle(EMAIL_LINK_CLICKED).resolve(request.secret()); } } ``` ```python Python {"CODE_LOAD::python/src/foundations/services/workflow.py#here"} theme={null} signup_workflow = restate.Workflow("UserSignup") @signup_workflow.main() async def run(ctx: WorkflowContext, user: User) -> bool: # workflow ID = user ID; workflow runs once per user user_id = ctx.key() await ctx.run_typed("create", create_user_entry, user_id=user_id, user=user) secret = str(ctx.uuid()) await ctx.run_typed("mail", send_verification_email, user=user, secret=secret) click_secret = await ctx.promise("email-link-clicked").value() return click_secret == secret @signup_workflow.handler() async def click(ctx: WorkflowSharedContext, secret: str) -> None: await ctx.promise("email-link-clicked").resolve(secret) ``` ```go Go {"CODE_LOAD::go/foundations/services/workflow.go#here"} theme={null} type SignupWorkflow struct{} func (SignupWorkflow) Run(ctx restate.WorkflowContext, user User) (bool, error) { // workflow ID = user ID; workflow runs once per user userId := restate.Key(ctx) _, err := restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return createUserEntry(userId, user) }) if err != nil { return false, err } secret := restate.Rand(ctx).UUID().String() _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return sendVerificationEmail(user, secret) }) if err != nil { return false, err } clickSecret, err := restate.Promise[string](ctx, "email-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, "email-link-clicked").Resolve(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. ```typescript TypeScript {"CODE_LOAD::ts/src/foundations/services/app.ts#here"} theme={null} restate.serve({ services: [subscriptionService, cartObject, signupWorkflow], port: 9080, }); ``` ```java Java {"CODE_LOAD::java/src/main/java/foundations/services/App.java#here"} theme={null} public class App { public static void main(String[] args) { RestateHttpServer.listen( Endpoint.bind(new SubscriptionService()) .bind(new ShoppingCartObject()) .bind(new SignupWorkflow())); } } ``` ```python Python {"CODE_LOAD::python/src/foundations/services/app.py#here"} theme={null} app = restate.app([subscription_service, cart_object, signup_workflow]) if __name__ == "__main__": conf = hypercorn.Config() conf.bind = ["0.0.0.0:9080"] asyncio.run(hypercorn.asyncio.serve(app, conf)) ``` ```go Go {"CODE_LOAD::go/foundations/services/app.go#here"} theme={null} func main() { if err := server.NewRestate(). Bind(restate.Reflect(SubscriptionService{})). Bind(restate.Reflect(ShoppingCartObject{})). Bind(restate.Reflect(SignupWorkflow{})). Start(context.Background(), ":9080"); err != nil { log.Fatal(err) } } ``` 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: ```shell theme={null} 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](/deploy/services/kubernetes) and [versioning](/services/versioning) docs for details. # Local Restate Cluster with Docker Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/cluster Learn how to deploy a Restate cluster using Docker Compose. This guide shows how to deploy a distributed Restate cluster consisting of three nodes using Docker and Docker compose. * [Docker](https://docs.docker.com/get-started/get-docker/) * [Docker Compose](https://docs.docker.com/compose/install/) To deploy a 3-node distributed Restate cluster, create a file `docker-compose.yml` and run `docker compose up`. ```yaml docker-compose.yml theme={null} x-environment: &common-env RESTATE_CLUSTER_NAME: "restate-cluster" # For more on logging, see: https://docs.restate.dev/server/monitoring/logging RESTATE_LOG_FILTER: "restate=info" RESTATE_DEFAULT_REPLICATION: 2 # We require minimum of 2 nodes to accept writes # The addresses where nodes can reach each other over the "internal" Docker Compose network RESTATE_METADATA_CLIENT__ADDRESSES: '["http://restate-1:5122","http://restate-2:5122","http://restate-3:5122"]' # Partition snapshotting, see: https://docs.restate.dev/server/snapshots RESTATE_WORKER__SNAPSHOTS__DESTINATION: "s3://restate/snapshots" RESTATE_WORKER__SNAPSHOTS__SNAPSHOT_INTERVAL_NUM_RECORDS: "1000" RESTATE_WORKER__SNAPSHOTS__AWS_REGION: "local" RESTATE_WORKER__SNAPSHOTS__AWS_ENDPOINT_URL: "http://minio:9000" RESTATE_WORKER__SNAPSHOTS__AWS_ALLOW_HTTP: true RESTATE_WORKER__SNAPSHOTS__AWS_ACCESS_KEY_ID: "minioadmin" RESTATE_WORKER__SNAPSHOTS__AWS_SECRET_ACCESS_KEY: "minioadmin" x-defaults: &defaults image: docker.restate.dev/restatedev/restate:latest extra_hosts: - "host.docker.internal:host-gateway" volumes: - restate-data:/restate-data services: restate-1: <<: *defaults ports: - "8080:8080" # Ingress - "9070:9070" # Admin - "5122:5122" # Node-to-node communication environment: <<: *common-env RESTATE_NODE_NAME: restate-1 RESTATE_FORCE_NODE_ID: 1 RESTATE_ADVERTISED_ADDRESS: "http://restate-1:5122" # Other Restate nodes must be able to reach us using this address RESTATE_AUTO_PROVISION: "true" # Only the first node provisions the cluster restate-2: <<: *defaults ports: - "25122:5122" - "29070:9070" - "28080:8080" environment: <<: *common-env RESTATE_NODE_NAME: restate-2 RESTATE_FORCE_NODE_ID: 2 RESTATE_ADVERTISED_ADDRESS: "http://restate-2:5122" RESTATE_AUTO_PROVISION: "false" restate-3: <<: *defaults ports: - "35122:5122" - "39070:9070" - "38080:8080" environment: <<: *common-env RESTATE_NODE_NAME: restate-3 RESTATE_FORCE_NODE_ID: 3 RESTATE_ADVERTISED_ADDRESS: "http://restate-3:5122" RESTATE_AUTO_PROVISION: "false" minio: image: quay.io/minio/minio entrypoint: "/bin/sh" # Ensure a bucket called "restate" exists on startup: command: "-c 'mkdir -p /data/restate && /usr/bin/minio server --quiet /data'" ports: - "9000:9000" # We create a volume to persist data across container starts; delete it via `docker volume rm restate-data` if you want to start a fresh cluster volumes: restate-data: ``` The cluster uses the `replicated` Bifrost provider and replicates log writes to a minimum of two nodes. Since we are running with 3 nodes, the cluster can tolerate one node failure without becoming unavailable. By default, partition state is replicated to all workers (though each partition has only one acting leader at a time). The `replicated` metadata cluster consists of all nodes since they all run the `metadata-server` role. Since the `replicated` metadata cluster requires a majority quorum to operate, the cluster can tolerate one node failure without becoming unavailable. Take a look at the [cluster deployment documentation](/deploy/server/cluster) for more information on how to configure and deploy a distributed Restate cluster. In this example we also deployed a Minio server to host the cluster snapshots bucket. Visit [Snapshots](/server/snapshots) to learn more about whis is strongly recommended for all clusters. You can check the status of the cluster by running the `restatectl status` command on any of the started Restate servers. Note, it might take a few seconds until the cluster has fully started and the status is available. ```shell theme={null} docker compose exec restate-1 restatectl status ``` Follow the [Quickstart guide](/quickstart) to create a simple Restate service. You can register the service endpoint at any of the started Restate nodes since they all run the `admin` role. ```shell theme={null} restate dp register http://host.docker.internal:9080 ``` Or alternatively you can open the Restate UI at [http://localhost:9080](http://localhost:9080) and register the service endpoint there. You can invoke the registered service at any of the started Restate nodes since they all run the ingress. ```shell theme={null} curl localhost:8080/Greeter/greet --json '"Sarah"' && curl localhost:28080/Greeter/greet --json '"Bob"' && curl localhost:38080/Greeter/greet --json '"Eve"' ``` Try killing and restarting one of the Restate nodes and see how the cluster reacts. ```shell theme={null} docker compose kill restate-1 && sleep 5 && docker compose up -d restate-1 ``` Try instructing the partition processors to create a snapshot of their state in the object store bucket: ```shell theme={null} docker compose exec restate-1 restatectl snapshot create ``` Navigate to the Minio console at [http://localhost:9000](http://localhost:9000) and browse the bucket contents (default credentials: `minioadmin`/`minioadmin`). Here are some next steps for you to try: * Try to configure a 5-node Restate cluster that can tolerate up to two node failures. * Trim the logs (either manually, or by setting up automatic trimming) *before* adding more nodes. * Try to deploy a 3-node Restate cluster using Kubernetes. # Cron Jobs Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/cron Schedule tasks periodically with Restate A cron job is a scheduled task that runs periodically at a specified time or interval. It is often used for background tasks like cleanup or sending notifications. ## How does Restate help? Restate has many features that make it a good fit for implementing cron jobs: * **Durable timers**: Schedule tasks reliably for future execution. * **Task resiliency**: Automatic retries until success. * **Task control**: Inspect and cancel running jobs. * **K/V state**: Store and query cron job details using K/V storage. * **FaaS support**: Integrates with platforms like AWS Lambda, scaling to zero when idle. * **Scalability**: Handles many parallel jobs; scales horizontally. * **Observability**: View execution history and job status in the Restate UI. Restate doesn't have built-in cron functionality, but its building blocks make it easy to build reliable, custom schedulers. ## Example To implement cron jobs, you can copy over the following file ([TS](https://github.com/restatedev/examples/blob/main/typescript/patterns-use-cases/src/cron/cron_service.ts) / [Java](https://github.com/restatedev/examples/blob/main/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java) / [Go](https://github.com/restatedev/examples/blob/main/go/patterns-use-cases/src/cron/cron.go)) to your project: ```ts TypeScript expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/cron/cron_service.ts?collapse_prequel"} theme={null} export const cronJobInitiator = restate.service({ name: "CronJobInitiator", handlers: { create: async (ctx: restate.Context, request: JobRequest) => { // Create a new job ID and initiate the cron job object for that ID // We can then address this job object by its ID const jobId = ctx.rand.uuidv4(); const job = await ctx.objectClient(cronJob, jobId).initiate(request); return `Job created with ID ${jobId} and next execution time ${job.next_execution_time}`; }, }, }); export const cronJob = restate.object({ name: "CronJob", handlers: { initiate: async (ctx: restate.ObjectContext, request: JobRequest): Promise => { if (await ctx.get(JOB_STATE)) { throw new TerminalError("Job already exists for this ID."); } return await scheduleNextExecution(ctx, request); }, execute: async (ctx: restate.ObjectContext) => { const jobState = await ctx.get(JOB_STATE); if (!jobState) { throw new TerminalError("Job not found."); } // execute the task const { service, method, key, payload } = jobState.request; if (payload) { ctx.genericSend({ service, method, parameter: payload, key, inputSerde: serde.json, }); } else { ctx.genericSend({ service, method, parameter: undefined, key, inputSerde: serde.empty, }); } await scheduleNextExecution(ctx, jobState.request); }, cancel: async (ctx: restate.ObjectContext) => { // Cancel the next execution const jobState = await ctx.get(JOB_STATE); if (jobState) { ctx.cancel(jobState.next_execution_id); } // Clear the job state ctx.clearAll(); }, getInfo: restate.handlers.object.shared(async (ctx: restate.ObjectSharedContext) => { return ctx.get(JOB_STATE); }), }, }); const scheduleNextExecution = async ( ctx: restate.ObjectContext, request: JobRequest, ): Promise => { // Parse cron expression // Persist current date in Restate for deterministic replay const currentDate = await ctx.date.now(); let interval; try { interval = CronExpressionParser.parse(request.cronExpression, { currentDate }); } catch (e) { throw new TerminalError(`Invalid cron expression: ${(e as Error).message}`); } const next = interval.next().toDate(); const delay = next.getTime() - currentDate; // Schedule next execution for this job const thisJobId = ctx.key; // This got generated by the CronJobInitiator const handle = ctx.objectSendClient(cronJob, thisJobId, { delay }).execute(); // Store the job information const jobState = { request, next_execution_time: next.toString(), next_execution_id: await handle.invocationId, }; ctx.set(JOB_STATE, jobState); return jobState; }; ``` ```java Java expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/java/patterns-use-cases/src/main/java/my/example/cron/Cron.java"} theme={null} package my.example.cron; import static com.cronutils.model.CronType.UNIX; import com.cronutils.model.definition.CronDefinitionBuilder; import com.cronutils.model.time.ExecutionTime; import com.cronutils.parser.CronParser; import dev.restate.common.Request; import dev.restate.common.Target; import dev.restate.sdk.Context; import dev.restate.sdk.ObjectContext; import dev.restate.sdk.SharedObjectContext; import dev.restate.sdk.annotation.*; import dev.restate.sdk.common.StateKey; import dev.restate.sdk.common.TerminalException; import dev.restate.serde.TypeTag; import java.time.ZonedDateTime; import java.util.Optional; /* * A distributed cron service built with Restate that schedules tasks based on cron expressions. * * Features: * - Create cron jobs with standard cron expressions (e.g., "0 0 * * *" for daily at midnight) * - Schedule any Restate service handler or virtual object method * - Guaranteed execution with Restate's durability * - Cancel and inspect running jobs * * Usage: * 1. Send requests to CronInitiator.create() to start new jobs * 2. Each job gets a unique ID and runs as a CronJob virtual object * 3. Jobs automatically reschedule themselves after each execution */ public class Cron { public record JobRequest( String cronExpression, // e.g. "0 0 * * *" (every day at midnight) String service, String method, // Handler to execute with this schedule Optional key, // Optional Virtual Object key of the task to call Optional payload) {} // Optional data to pass to the handler public record JobInfo(JobRequest request, String nextExecutionTime, String nextExecutionId) {} @Name("CronJobInitiator") @Service public static class JobInitiator { @Handler public String create(Context ctx, JobRequest request) { // Create a new job ID and initiate the cron job object for that ID // We can then address this job object by its ID var jobId = ctx.random().nextUUID().toString(); var cronJob = CronJobClient.fromContext(ctx, jobId).initiate(request).await(); return String.format( "Job created with ID %s and next execution time %s", jobId, cronJob.nextExecutionTime()); } } @Name("CronJob") @VirtualObject public static class Job { private final StateKey JOB_STATE = StateKey.of("job-state", JobInfo.class); private final CronParser PARSER = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(UNIX)); @Handler public JobInfo initiate(ObjectContext ctx, JobRequest request) { if (ctx.get(JOB_STATE).isPresent()) { throw new TerminalException("Job already exists for this ID"); } return scheduleNextExecution(ctx, request); } @Handler public void execute(ObjectContext ctx) { JobRequest request = ctx.get(JOB_STATE).orElseThrow(() -> new TerminalException("Job not found")).request; executeTask(ctx, request); scheduleNextExecution(ctx, request); } @Handler public void cancel(ObjectContext ctx) { ctx.get(JOB_STATE) .ifPresent(jobState -> ctx.invocationHandle(jobState.nextExecutionId).cancel()); // Clear the job state ctx.clearAll(); } @Shared public Optional getInfo(SharedObjectContext ctx) { return ctx.get(JOB_STATE); } private void executeTask(ObjectContext ctx, JobRequest job) { Target target = (job.key.isPresent()) ? Target.virtualObject(job.service, job.method, job.key.get()) : Target.service(job.service, job.method); var request = (job.payload.isPresent()) ? Request.of( target, TypeTag.of(String.class), TypeTag.of(Void.class), job.payload.get()) : Request.of(target, new byte[0]); ctx.send(request); } private JobInfo scheduleNextExecution(ObjectContext ctx, JobRequest request) { // Parse cron expression ExecutionTime executionTime; try { executionTime = ExecutionTime.forCron(PARSER.parse(request.cronExpression)); } catch (IllegalArgumentException e) { throw new TerminalException("Invalid cron expression: " + e.getMessage()); } // Calculate next execution time var now = ctx.run(ZonedDateTime.class, ZonedDateTime::now); var delay = executionTime .timeToNextExecution(now) .orElseThrow(() -> new TerminalException("Cannot determine next execution time")); var next = executionTime .nextExecution(now) .orElseThrow(() -> new TerminalException("Cannot determine next execution time")); // Schedule next execution for this job String thisJobId = ctx.key(); // This got generated by the CronJobInitiator var handle = CronJobClient.fromContext(ctx, thisJobId).send().execute(delay); // Save job state var jobState = new JobInfo(request, next.toString(), handle.invocationId()); ctx.set(JOB_STATE, jobState); return jobState; } } } ``` ```go Go expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/cron/cron.go"} theme={null} package main import ( "fmt" "time" restate "github.com/restatedev/sdk-go" "github.com/robfig/cron/v3" ) // JobRequest represents the structure for creating a cron job type JobRequest struct { CronExpression string `json:"cronExpression"` // The cron expression e.g. "0 0 * * *" (every day at midnight) Service string `json:"service"` Method string `json:"method"` // Handler to execute with this schedule Key string `json:"key,omitempty"` // Optional: Virtual Object key of task to call Payload string `json:"payload,omitempty"` // Optional payload to pass to the handler } // JobInfo represents the stored job information type JobInfo struct { Request JobRequest `json:"request"` NextExecutionTime time.Time `json:"next_execution_time"` NextExecutionID string `json:"next_execution_id"` } const JOB_STATE = "job-state" // K/V state key for storing job info in the Restate // CronJobInitiator service for creating new cron jobs // // A distributed cron service built with Restate that schedules tasks based on cron expressions. // // Features: // - Create cron jobs with standard cron expressions (e.g., "0 0 * * *" for daily at midnight) // - Schedule any Restate service handler or virtual object method // - Guaranteed execution with Restate's durability // - Cancel and inspect running jobs // // Usage: // 1. Send requests to CronJobInitiator.Create() to start new jobs // 2. Each job gets a unique ID and runs as a CronJob virtual object // 3. Jobs automatically reschedule themselves after each execution type CronJobInitiator struct{} func (CronJobInitiator) Create(ctx restate.Context, req JobRequest) (string, error) { // Create a new job ID and initiate the cron job object for that ID // We can then address this job object by its ID jobID := restate.Rand(ctx).UUID().String() fmt.Printf("Creating new cron job with ID %s for service %s and method %s", jobID, req.Service, req.Method) job, err := restate.Object[*JobInfo](ctx, "CronJob", jobID, "Initiate").Request(req) if err != nil { return "", err } return fmt.Sprintf("Job created with ID %s and next execution time %s", jobID, job.NextExecutionTime.Format(time.RFC3339)), nil } type CronJob struct{} func (CronJob) Initiate(ctx restate.ObjectContext, req JobRequest) (*JobInfo, error) { // Check if jobState already exists jobState, err := restate.Get[*JobInfo](ctx, JOB_STATE) if err != nil { return nil, err } if jobState != nil { return nil, restate.TerminalErrorf("jobState already exists for this ID", 500) } return scheduleNextExecution(ctx, req) } func (CronJob) Execute(ctx restate.ObjectContext) error { // Get the job information jobState, err := restate.Get[*JobInfo](ctx, JOB_STATE) if err != nil { return err } if jobState == nil { return restate.TerminalErrorf("job not found", 500) } // Add key if it's a virtual object call req := jobState.Request fmt.Printf("Executing job with ID: %s for service %s for method %s", restate.Key(ctx), req.Service, req.Method) if req.Key != "" { restate.ObjectSend(ctx, req.Service, req.Key, req.Method).Send(req.Payload) } else { restate.ServiceSend(ctx, req.Service, req.Method).Send(req.Payload) } // Schedule the next execution _, err = scheduleNextExecution(ctx, req) return err } func (CronJob) Cancel(ctx restate.ObjectContext) error { // Get the job to cancel the next execution job, err := restate.Get[*JobInfo](ctx, JOB_STATE) if err != nil { return err } if job == nil { return restate.TerminalErrorf("job not found for cancellation", 404) } restate.CancelInvocation(ctx, job.NextExecutionID) restate.ClearAll(ctx) return nil } func (CronJob) GetInfo(ctx restate.ObjectSharedContext) (*JobInfo, error) { return restate.Get[*JobInfo](ctx, JOB_STATE) } // scheduleNextExecution calculates and schedules the next execution of the cron job func scheduleNextExecution(ctx restate.ObjectContext, req JobRequest) (*JobInfo, error) { // Parse cron expression parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) schedule, err := parser.Parse(req.CronExpression) if err != nil { return nil, restate.TerminalErrorf("invalid cron expression: %v", err, 500) } // Get current time deterministically from Restate currentTime, _ := restate.Run(ctx, func(ctx restate.RunContext) (time.Time, error) { return time.Now(), nil }) // Calculate next execution time nextTime := schedule.Next(currentTime) delay := nextTime.Sub(currentTime) // Schedule next execution for this job thisJobID := restate.Key(ctx) // This got generated by the CronJobInitiator handle := restate.ObjectSend(ctx, "CronJob", thisJobID, "Execute").Send(nil, restate.WithDelay(delay)) // Store the job information jobState := &JobInfo{ Request: req, NextExecutionTime: nextTime, NextExecutionID: handle.GetInvocationId(), } restate.Set(ctx, JOB_STATE, jobState) return jobState, nil } ``` This contains two services: a `CronJobInitiator` Service, and a `CronJob` Virtual Object: Cron Jobs Diagram Bind both services to your endpoint, and register them. Usage: * Send requests to `CronJobInitiator.create()` to start new jobs with standard [cron expressions](https://www.baeldung.com/cron-expressions): ```json theme={null} { "cronExpression": "0 0 * * *", # E.g. run every day at midnight "service": "TaskService", # Schedule any Restate handler "method": "executeTask", "key": "taskId", # Optional, Virtual Object key "payload": "Hello midnight!" } ``` * Each job gets a unique ID and runs as a CronJob Virtual Object. * Jobs automatically reschedule themselves after each execution. This pattern is implementable with any of our SDKs. We are still working on translating all patterns to all SDK languages. If you need help with a specific language, please reach out to us via [Discord](https://discord.com/invite/skW3AZ6uGd) or [Slack](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA). ## Running the example ```bash TypeScript theme={null} restate example typescript-patterns-use-cases && cd typescript-patterns-use-cases ``` ```bash Java theme={null} restate example java-patterns-use-cases && cd java-patterns-use-cases ``` ```bash Go theme={null} restate example go-patterns-use-cases && cd go-patterns-use-cases ``` ```bash theme={null} restate-server ``` ```bash TypeScript theme={null} npm install npx tsx watch ./src/cron/task_service.ts ``` ```bash Java theme={null} ./gradlew -PmainClass=my.example.cron.TaskService run ``` ```bash Go theme={null} go run ./src/cron ``` ```bash theme={null} restate deployments register localhost:9080 ``` For example, run `executeTask` every minute: ```bash TypeScript theme={null} curl localhost:8080/CronJobInitiator/create --json '{ "cronExpression": "* * * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello new minute!" }' ``` ```bash Java theme={null} curl localhost:8080/CronJobInitiator/create --json '{ "cronExpression": "* * * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello new minute!" }' ``` ```bash Go theme={null} curl localhost:8080/CronJobInitiator/Create --json '{ "cronExpression": "* * * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello new minute!" }' ``` For example, or run `executeTask` at midnight: ```bash TypeScript theme={null} curl localhost:8080/CronJobInitiator/create --json '{ "cronExpression": "0 0 * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello midnight!" }' ``` ```bash Java theme={null} curl localhost:8080/CronJobInitiator/create --json '{ "cronExpression": "0 0 * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello midnight!" }' ``` ```bash Go theme={null} curl localhost:8080/CronJobInitiator/Create --json '{ "cronExpression": "0 0 * * *", "service": "TaskService", "method": "executeTask", "payload": "Hello midnight!" }' ``` You can also use the cron service to execute handlers on Virtual Objects, by specifying the Virtual Object key in the request. You will get back a response with the job ID. Using the job ID, you can then get information about the job: ```bash TypeScript theme={null} curl localhost:8080/CronJob/myJobId/getInfo ``` ```bash Java theme={null} curl localhost:8080/CronJob/myJobId/getInfo ``` ```bash Go theme={null} curl localhost:8080/CronJob/myJobId/GetInfo ``` Or cancel the job later: ```bash TypeScript theme={null} curl localhost:8080/CronJob/myJobId/cancel ``` ```bash Java theme={null} curl localhost:8080/CronJob/myJobId/cancel ``` ```bash Go theme={null} curl localhost:8080/CronJob/myJobId/Cancel ``` In the UI, you can see how the tasks are scheduled, and how the state of the cron jobs is stored in Restate. Cron Service Schedule Cron State UI You can kill and restart any of the services or the Restate Server, and the scheduled tasks will still be there. ## Adapt to your use case Note that this implementation is fully resilient, but you might need to make some adjustments to make this fit your use case: * Take into account time zones. * Adjust how you want to handle tasks that fail until the next task gets scheduled. With the current implementation, you would have concurrent executions of the same cron job (one retrying and the other starting up). If you want to cancel the failing task when a new one needs to start, you can do the following: at the beginning of the `execute` call, retrieve the `next_execution_id` from the job state and check if it is completed by [attaching to it](/develop/ts/service-communication#re-attach-to-an-invocation) with [a timeout set to 0](/develop/ts/durable-timers#timers-and-timeouts). If it is not completed, [cancel it](/develop/ts/service-communication#cancel-an-invocation) and start the new iteration. # Databases and Restate Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/databases Learn when and how to use databases in combination with Restate. Restate is not only a system for resilience and durability in communication and orchestration, it can also store data like a database system, to enable writing long-lived stateful logic with strong out-of-the-box resilience. To avoid confusion: In this page, we refer to the type of state that outlives a single workflow or durable handler execution, and that uses Restate’s [Virtual Objects](/foundations/services#virtual-object). This is a feature not available in typical workflow systems or durable execution frameworks. This guide discusses thoughts on when to use Restate for that type of state, when to use a database, and how to integrate Restate and databases. ## What state to store in Restate, what to store in databases State stored directly in Restate ([Virtual Objects](/foundations/services#virtual-object)) has a set of advantages: * **Ultra robust**: No coordination between systems is needed. Instead, state access/updates directly participate in the durable execution logic (go through the same consensus log), making the state always aligned with the business logic: no lost updates, duplications of state changes, stale views of state, etc. * **Simple correctness model**: The single-writer-per-key and linearizable consistency model means developers don’t need to worry about locks, race conditions, dirty reads, change visibility, zombie processes, etc. * **Efficient**: Computation and access patterns to state align naturally, you cannot get into situations where multiple function executions try to access/update the same database entry and contend on locks or interfere with each other's transaction. No write amplification through separate durability in an external system (instead state is simply committed with the durable execution journal data). * **Serverless**: when Restate invokes a handler, it attaches the relevant state to the request. On serverless, for example AWS lambda, this means your state is locally available when the function executes, no access delays, synchronization, wait times, etc. * **No additional dependency** - the K/V store is integrated in the core of Restate. You don't need to do anything extra to start using state when developing locally, or migrating the Restate app to cloud/production. State stored directly in Restate (Virtual Objects) has some limitations: * K/V interface with single-key transactions (though a single key can store a document with flexible structures) * SQL is supported only for analytics / introspection, not for updates/transactions. * Modifiable only from the Virtual Object, not from other services. Any modification needs to be sent as a request to the Virtual Object. * While state can be exported in a standard way (psql client), it is managed by a less standard system, compared to some databases. ## When to use which? Use a database: * When you need complex access patterns, full SQL, text search, time-series analysis, etc. * For core business data, like your user database, products database, history of transactions, etc. that you want to access from other services as well Use Restate for any state requiring tight integration with function logic, resilience, and correctness: * State that is part of transactional state machines (payment status, activation status, ...) * Session state (shopping cart, ongoing orders, LLM context, AI agent context, ...) * State that is part of distributed orchestration and coordination (e.g., versions/life-cycle of shared resources, transaction IDs, reference counts, distributed locks/semaphores/leases, ...) * Agents / Digital-twins (agent/twin memory and context) * State in event processing pipelines (aggregates, joins) * State of control planes (desired cluster status, resource references, ...) * Consistent state/metadata overlay over eventual consistent infra ## How to interact with Databases from Restate This set of examples shows various patterns to access databases from Restate handlers. ```ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/database/main.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; import { Sequelize, Transaction } from "sequelize"; import { runQueryAs2pcTxn } from "./2phasecommit"; // ---------------------------------------------------------------------------- // // This example shows various patterns to access databases from Restate // handlers. // // The basics are: // // (1) You don't need to do anything special, you can just interact with // your database the same way as from other microservices or // workflow activities. // // (2) But you can use Restate's state, journal, and concurrency mechanisms // as helpers to improve common access problems, solve race conditions, // or avoid inconsistencies in the presence of retries, concurrent requests, // or zombie processes. // // The below example illustrate this step by step. // // ---------------------------------------------------------------------------- type Row = { userId: string; // the primary key name: string; address: string; credits: number; version?: number; // the row version (used by some access methods) }; const db = new Sequelize("postgres://restatedb:restatedb@localhost:5432/exampledb", { logging: false, }); // ---------------------------------------------------------------------------- /** * This service implements the database accesses similar to the way you * might access the database from other microservices. * It doesn't use any Restate features to help making the DB access consistent. */ const simpledbAccess = restate.service({ name: "simple", handlers: { /** * Simple read from a database row. * This is the same as it would be from any non-Restate service. */ read: async (_ctx: restate.Context, userid: string) => { const [results, _meta] = await db.query( `SELECT * FROM users WHERE userid = '${userid}'`, ); return results.length > 0 ? results[0] : "(row not found)"; }, /** * This performs a read in a Restate durable action, persisting the result. * The read access is the same as in a non-Restate service, but the result * is persisted in the execution journal. * * This is useful for example, if you have decisions/branches based on * the result and want to ensure consistent and deterministic behavior * on retries (where the value in the database might have changed * in-between retries). */ durableRead: async (ctx: restate.Context, userid: string) => { const credits = await ctx.run("read credits", async () => { const [results, _metadata] = await db.query( `SELECT credits FROM users WHERE userid = '${userid}'`, ); return (results[0] as any)?.credits; }); if (credits === 0) { // Execute some conditional logic, e.g., alerting, sending reminder. // Automatic retries (on failure) will follow this branch again (using // Restate's journalled value for 'credits') so code in this branch will // reliably run to conclusion console.log("Found an account without balance, doing something about it..."); } return credits; }, /** * Inserts a row into the database. No Restate-specific handling, this * simply uses the database's primary key uniqueness to avoid duplicate * entries. */ insert: async (_ctx: restate.Context, row: Row) => { const { userId, name, address, credits } = row; const [_, numInserted] = await db.query( `INSERT INTO users (userid, name, address, credits, version) VALUES ('${userId}', '${name}', '${address}', ${credits}, 0) ON CONFLICT (userid) DO NOTHING`, ); return numInserted === 1; }, /** * Updates a row in a database. This handler makes no specific effort to * add idempotency, so it purely relies on database / application semantics. * It is vulnerable to concurrent updates overwriting each other, but can make * sense if this is fine for a specific type of (rare) update. * * For an example that use Restate to add consistency, see the examples * further below. */ update: async (_ctx: restate.Context, update: { userId: string; newName: string }) => { const { userId, newName } = update; // Update row - we execute this statement directly, because it is the only // step in this handler. If the handler contains multiple steps, wrap this in // a `ctx.run( () => { ... } )` block to ensure it does not get re-executed once // during retries of successive steps const [_, meta] = await db.query( `UPDATE users SET name = '${newName}' WHERE userID = '${userId}'`, ); return (meta as any).rowCount === 1; }, }, }); // ---------------------------------------------------------------------------- /* * When updating single rows by primary key (on a database or a key/value store), * you can use Virtual Objects to serialize access per key. This happens automatically * if you access the database from a Virtual Object and use the same key for object and * primary key in the database. * * Most databases internally serialize updates on the same row anyways (either through locks, * or by detecting conflicts during when attempting to commit transactions and rolling back), so * making the access sequential from the application can help to avoid lock congestion and * avoid pathological degradation of optimistic concurrency control around contended keys. * * Furthermore, this update pattern makes it possible to use conditional updates with versions * that don't get duplicated on retries, resulting in proper exactly-once semantics. */ const keyedDbAccess = restate.object({ name: "keyed", handlers: { /** * Updates the credits for a row in a database. * This variant makes no effort to ensure the update does not get duplicated. * * In practice, this will can reasonably safe, because there is a very small window * time where the query gets re-executed after success, whihc is when the query succeeded, * but Restate did not see the completion of the handler. * * Note that this is especially rare, since the single-writer-per-key semantics of the * Virtual Object ensure that no contention on rows can happen in the database. */ update: async (ctx: restate.ObjectContext, credits: number) => { const userid = ctx.key; await db.query( `UPDATE users SET credits = credits + ${credits} WHERE userid = '${userid}'`, ); }, /** * Updates the 'credits' field and uses the 'version' number to make the update * idempotent. * * The approach is to separate reads (current credits, version) and * updates into separate queries. The reads are kept in the execution journal, and * the updates are conditional on the fact that the version did not change in between. * This is sometimes referred to as a _semantic lock pattern_. * * Because the reads go through a `ctx.run` step (strong consensus inside Restate), * it is guaranteed that the same invocation will always work off of the same version. * Regardless of partial failures/retries/zombies/network partitions, the code will * always observe a single version. * * If all updates to the row go through this handler (or other handlers in this * Virtual Object that use the versioning scheme) then no duplicates are possible. */ updateConditional: async (ctx: restate.ObjectContext, addCredits: number) => { const userid = ctx.key; // first read the current credits and the version. // Because the reads go through a `ctx.run` step (strong consensus), the // a single invocation and all if its retries can never see different values // for version and credits. const [credits, version] = await ctx.run("read credits, version", async () => { const [results, _] = await db.query( `SELECT credits, version FROM users WHERE userid = '${userid}'`, ); if (results.length !== 1) { throw new restate.TerminalError(`No single row with userid = '${userid}'`); } const row = results[0] as any; return [Number(row.credits), Number(row.version)]; }); // Make the update conditional on the fact that the version is still the same (see // 'AND version = ...'). If the version is no longer the same, it means that a // previous execution attempt updated this and crashed before completing the handler). // It cannot be completed by a concurrent query, because all operations on the userid // are serialized through the Virtual Object. const [_, meta] = await db.query( `UPDATE users SET credits = ${credits + addCredits}, version = ${version + 1} WHERE userid = '${userid}' AND version = ${version}`, ); if ((meta as any).rowCount !== 1) { console.debug("Update was not applied - version no longer matches."); } }, /** * Reads a row from the database. * * This one is markes as a SHARED handler, which means it doesn't queue * with the other invocations for this key. That way reads execute immediately * and concurrently, while writes execute sequentially. */ read: restate.handlers.object.shared(async (ctx: restate.ObjectSharedContext) => { const userid = ctx.key; const [results, _meta] = await db.query( `SELECT * FROM users WHERE userid = '${userid}'`, ); return results[0] ?? "(row not found)"; }), }, }); // ---------------------------------------------------------------------------- /** * This service uses a second table in the database to remember idempotency tokens * for each operation. The idempotency tokens are inserted into the database in the * same transaction as the main operation. * * The code uses Restate's features to deterministically generate the tokens and * to expire them after a day. */ const idempotencyKeyDbAccess = restate.service({ name: "idempotency", handlers: { /** * Adds credits to a single user entry. This operation is _idempotent_ within a * time window of a day. * * This handler uses a second table in the database to remember idempotency tokens * for each update. The idempotency tokens are inserted into the database in the * same transaction as the update. * * The code uses Restate's features to deterministically generate the tokens and * to expire them after a day. */ update: async (ctx: restate.Context, update: { userId: string; addCredits: number }) => { // create a persistent idempotency key. we use Restate's random number generator, // because that is the most efficient way to generate a durable deterministic ID. const idempotencyKey = ctx.rand.uuidv4(); // expire the idempotency key from the database after one day // by scheduling a call to the handler that deletes the key ctx .serviceSendClient(idempotencyKeyDbAccess) .expireIdempotencyKey(idempotencyKey, restate.rpc.sendOpts({ delay: { days: 1 } })); // checking the existence of the idempotency key and making the update happens // in one database transaction const tx = await db.transaction({ isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, }); try { // first test whether the idempotency token exists by checking whether // we can insert one into the table (primary key constraint prevents // duplicates) const [_tokenRows, numInserted] = await db.query( `INSERT INTO user_idempotency (id) VALUES ('${idempotencyKey}') ON CONFLICT (id) DO NOTHING`, { transaction: tx }, ); if (numInserted !== 1) { // idempotency key was inserted before, so this must be a retry await tx.rollback(); return; } // now make the actual update await db.query( `UPDATE users SET credits = credits + ${update.addCredits} WHERE userid = '${update.userId}'`, { transaction: tx }, ); // if everything succeeds, commit idempotency token and update atomically await tx.commit(); } catch (e) { // speed up release of DB locks by eagerly aborting transaction await tx.rollback(); throw e; } }, /** * Deletes the idempotency key from the database. * This handler is involked as a scheduled call from the handler that performs * the update. */ expireIdempotencyKey: async (_ctx: restate.Context, key: string) => { await db.query( `DELETE FROM user_idempotency WHERE id = '${key}'`, ); }, }, }); // ---------------------------------------------------------------------------- /** * Exactly-once updates to the database using a 2-phase commit integration between * Restate and PostegreSQL. * * This uses PostgreSQL's PREPARE TRANSACTION feature to ensure that Restate * kicks off a transaction once and only once, and it thus works without any * additional idempotency or versioning mechanisms. * BUT: It requires the Postgres database to enable prepared transactions in the * configuration via `max_prepared_transactions = 100`. * * This is a _good use case_ of the 2-phase-commit protocol, because it is not * the (rightfully) criticized use case of running distributed transactions across * services and thus couple availability and liveliness of multiple services together. * This case here keeps the same participants and dependencies and only uses 2pc as a * way to split a single-participant transaction into two stages so that Restate can * avoid executing the query against the database multiple times under retries. * * NOTE: This code currently works only on services which have a bidi streaming connection, * (so any service deployed as http/2 or http1 bidi) due to the fact that it needs to * differentiate re-tries of blocks from first executions, which currently works only * for streaming connections. This is simply a missing feature and not a fundamental * limitation. */ const twoPhaseCommitDbAccess = restate.service({ name: "twoPhaseCommit", handlers: { update: async (ctx: restate.Context, update: { userId: string; addCredits: number }) => { const { userId, addCredits } = update; const query = `UPDATE users SET credits = credits + ${addCredits} WHERE userid = '${userId}'`; await runQueryAs2pcTxn(ctx, db, query); }, }, }); // ---------------------------------------------------------------------------- restate.serve({ services: [simpledbAccess, keyedDbAccess, idempotencyKeyDbAccess, twoPhaseCommitDbAccess], port: 9080, }); ``` ### Two-phase commit ```ts {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/database/2phasecommit.ts"} theme={null} import * as restate from "@restatedev/restate-sdk"; import { randomUUID } from "node:crypto"; import { Sequelize, Transaction } from "sequelize"; /** * Example 2-phase-commit integration library for Restate and PostegreSQL. * * This uses PostgreSQL's PREPARE TRANSACTION feature to ensure that Restate * kicks off a transaction once and only once. * * Postgres' interface is far from optimal here, for example it does not provide a way * to block/fence off discarded transaction IDs or establish other relationship between * transaction IDs. As a result, this code is needs to be very aggressive in cleaning * up previous transactions, to avoid leaving database locks in place. * * This code currently works only on services which have a bidi streaming connection !!! */ export async function runQueryAs2pcTxn( ctx: restate.Context, dbConnection: Sequelize, query: string, ) { const action = async (db: Sequelize, txn: Transaction) => { await db.query(query, { transaction: txn }); }; return runAs2pcTxn(ctx, dbConnection, action); } /** * See docs for {@link runQueryAs2pcTxn}. * * IMPORTANT: When using function, all queries issues must make use of the `tnx` object: * * await db.query( * "UDATE ... SET ... WHERE ...", * { transaction: txn } * ); * */ export async function runAs2pcTxn( ctx: restate.Context, dbConnection: Sequelize, action: (dbConnection: Sequelize, txn: Transaction) => Promise, ) { // this code only works on a streaming bi-directional connection, see below. checkRunsOnBidi(ctx); // We loop here until we are are sure that there successfully made it through // (1) running DB transaction, (2) preparing for commit (pre-commit), (3) recording // that in the Restate journal. // If we don't make it through the full squence in one execution attempt, we clear the // pending transaction and try again under a different transaction ID. Successive re-tries // and recoveries from failures also make sure they clean up the pending transaction. // In the absence of failures, the first iteration will succeed. let txnIdToCommit: string | undefined = undefined; while (txnIdToCommit === undefined) { // We generate the transaction ID and use a trick here to find out if we // actually generated the ID, or whether the ID was restored from the journal. // That is the reason this code only works on long-lived bidi connections (not Lambda), // because on Lambda, every `ctx.run` block is only ack-ed through a re-invocation // and journal replay. let executedThisTime: boolean = false; const txnId: string = await ctx.run("generate transaction id", () => { executedThisTime = true; return randomUUID(); }); // this is our cleanup hook for this specific transaction ID, to run in case // we need to ensure the transaction is cleared async function cleanup() { try { await dbConnection.query(`ROLLBACK PREPARED '${txnId}'`); } catch (e: any) { if (e.original?.code === "42704" && e instanceof Error) { // code 42704 means "no txn with that ID found". // => transaction was not prepared (crashed before) or already rolled back // => we can ignore this return; } console.error(e.message); throw e; } } try { const txnRan = await ctx.run("run txn attempt (or cleanup old txn)", async () => { // If we did not generate this transaction ID, a previous execution attempt did. // We don't know whether that execution made it to the point that it already prepared // the query (pre-committed) and crashed just before recording the completion of the // `ctx.run` block in the Restate journal. As a result, we clean up that transaction ID // to be on the safe side and not leave lingering database locks. if (!executedThisTime) { cleanup(); return false; // try again with a different txnId } // run: // (1) the query transaction // (2) pre-commit step (prepare transaction) // (3) record this in the Restate journal const txn = await dbConnection.transaction(); try { // (1) run the main app-defined database query (or queries) await action(dbConnection, txn); // (2) pre-commit - after this step, the txn is immutable and not rolled back // any more after closing/disposing/loss-of-connection await dbConnection.query(`PREPARE TRANSACTION '${txnId}'`, { transaction: txn, }); // (3) if this durable block returns ('true' recoded in journal) step (3) will // be completed return true; } catch (e) { await txn.rollback(); throw e; } }); if (txnRan) { txnIdToCommit = txnId; } } catch (e) { // clean up in case the query didn't go through, to speed up release // of possibly prepared txn await cleanup(); throw e; } } // now commit the prepared transaction - this step is idempotent, so if it was // already committed, this does nothing await ctx.run("commit prepared transaction", () => dbConnection.query(`COMMIT PREPARED '${txnIdToCommit}'`), ); } function checkRunsOnBidi(ctx: restate.Context): boolean { // this needs to be replaced by the actual check, which needs an additional // property to be exposed in Restate return true; // dummy value for now } ``` ## Running the examples This is purely optional, the example code and comments document the behavior well. Running the example can be interesting, though, if you want to play with specific failure scenarios, like pausing/killing processes at specific points and observe the behavior. [Check out the readme of the example on how to run the example.](https://github.com/restatedev/examples/blob/main/typescript/patterns-use-cases/README.md#database-interaction-patterns) # Durable Webhooks Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/durable-webhooks Process webhook events from external services with exactly-once delivery guarantees. Restate handlers can be used as **durable processors of webhook events**. ## How does Restate help? * Restate persists all incoming events, and ensures that they are **processed exactly once**, across failures and restarts. Restate guarantees your handler runs till completion. * Let Restate **deduplicate** events on an [idempotency key](/invoke/http#invoke-a-handler-idempotently). If the sender of the event retries, Restate will not process the event again. * Use any of Restate's [**durable SDK constructs**](/concepts/durable_building_blocks) when processing the events: durable calls/messaging to other services, durable timers, scheduling tasks, K/V state, concurrency guarantees etc. * Any handler can be a durable webhook endpoint. **No need to do anything special or extra!** Just point your webhook endpoint to your handler: `restate:8080/MyService/myHandler`. ## Example This example processes webhook callbacks from a payment provider. The payment provider notifies us about payment success or failure of invoices by sending webhook events to our handler. The handler then routes the event to the correct processor via a one-way message. ```ts TypeScript {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/webhookcallbacks/webhook_callback_router.ts?collapse_prequel"} theme={null} const webhookCallbackRouter = restate.service({ name: "WebhookCallbackRouter", handlers: { // Any handler can be a durable webhook processor that never loses events // You don't need to do anything special for this. // Just point your webhook to the handler endpoint: restate:8080/WebhookCallbackRouter/onStripeEvent onStripeEvent: async (ctx: restate.Context, event: StripeEvent) => { if (event.type === "invoice.payment_failed") { ctx.objectSendClient(PaymentTracker, event.data.object.id).onPaymentFailed(event); } else if (event.type === "invoice.payment_succeeded") { ctx.objectSendClient(PaymentTracker, event.data.object.id).onPaymentSuccess(event); } }, }, }); restate.serve({ services: [webhookCallbackRouter], port: 9080, }); ``` ```go Go {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/webhookcallbacks/callbackrouter.go?collapse_prequel"} theme={null} type WebhookCallbackRouter struct{} // Any handler can be a durable webhook processor that never loses events // You don't need to do anything special for this. // Just point your webhook to the handler endpoint: restate:8080/WebhookCallbackRouter/OnStripeEvent func (WebhookCallbackRouter) OnStripeEvent(ctx restate.Context, event StripeEvent) error { if event.Type == "invoice.payment_failed" { restate.ObjectSend(ctx, "PaymentTracker", "onPaymentFailed", event.Data.Object.ID). Send(event) } else if event.Type == "invoice.payment_succeeded" { restate.ObjectSend(ctx, "PaymentTracker", "onPaymentSuccess", event.Data.Object.ID). Send(event) } return nil } ``` This pattern is implementable with any of our SDKs. We are still working on translating all patterns to all SDK languages. If you need help with a specific language, please reach out to us via [Discord](https://discord.com/invite/skW3AZ6uGd) or [Slack](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA). # Error Handling Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/error-handling Learn how to handle transient and terminal errors in your applications. Restate handles retries for failed invocations. By default, Restate infinitely retries all errors with an exponential backoff strategy. This guide helps you fine-tune the retry behavior for your use cases. ## Infrastructure errors (transient) vs. application errors (terminal) In Restate, we distinguish between two types of errors: transient errors and terminal errors. * **Transient errors** are temporary and can be retried. They are typically caused by infrastructure issues (network problems, service overload, API unavailability,...). * **Terminal errors** are permanent and should not be retried. They are typically caused by application logic (invalid input, business rule violation, ...). ## Handling transient errors via retries Restate assumes by default that all errors are transient errors and therefore retryable. If you do not want an error to be retried, you need to specifically label it as a terminal error ([see below](#application-errors-terminal)). Restate lets you configure the retry strategy at different levels: for the invocation and at the run-block-level. ### At Invocation level Restate retries executing invocations that can't make any progress according to a retry policy. This policy controls the retry intervals, the maximum number of attempts and whether to **pause** or **kill** the invocation when the attempts are exhausted. The retry policy can be set on each individual handler, or for all the handlers of a service, or globally in the Restate configuration directly. To configure the retry policy on a service/handler level, check [retry service configuration](/services/configuration#retries). Via the [`restate-server` configuration file](/server/configuration): ```toml restate.toml theme={null} [default-retry-policy] initial-interval = "10s" max-attempts = 100 max-interval = "60s" ``` Then run the Restate Server with: ```shell theme={null} restate-server --config-file restate.toml ``` Or you can set these options via env variables: ```dotenv theme={null} RESTATE_DEFAULT_RETRY_POLICY__INITIAL_INTERVAL="10s" RESTATE_DEFAULT_RETRY_POLICY__MAX_ATTEMPTS=100 RESTATE_DEFAULT_RETRY_POLICY__MAX_INTERVAL="10s" ``` This retry policy will retry the invocation 100 times, after which the invocation will be paused if no progress can be made. To resume a paused invocation, check the paragraph below. You can also retry forever, without ever pausing or killing the invocation: ```dotenv theme={null} RESTATE_DEFAULT_RETRY_POLICY__MAX_ATTEMPTS=unlimited ``` Check the [configuration documentation](/server/configuration) and the [`default-retry-policy` reference](/references/server-config#param-default-retry-policy). When a retry policy is unset, Restate by default will retry undefinitely, alike setting `max-attempts = "unlimited"`. ### At the Run-Block-Level Handlers use run blocks to execute non-deterministic actions, often involving other systems and services (API call, DB write, ...). These run blocks are especially prone to transient failures, and you might want to configure a specific retry policy for them. ```ts TypeScript {"CODE_LOAD::ts/src/guides/retries.ts#here"} theme={null} const myRunRetryPolicy = { initialRetryInterval: { milliseconds: 500 }, retryIntervalFactor: 2, maxRetryInterval: { seconds: 1 }, maxRetryAttempts: 5, maxRetryDuration: { seconds: 1 }, }; await ctx.run("write", () => writeToOtherSystem(), myRunRetryPolicy); ``` ```python Python {"CODE_LOAD::python/src/guides/retries.py#here"} theme={null} await ctx.run_typed( "write", write_to_other_system, restate.RunOptions( # Max number of retry attempts to complete the action. max_attempts=3, # Max duration for retrying, across all retries. max_retry_duration=timedelta(seconds=10), ) ) ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/RetryRunService.java#here"} theme={null} RetryPolicy myRunRetryPolicy = RetryPolicy.exponential(Duration.ofMillis(500), 2) .setMaxDelay(Duration.ofSeconds(10)) .setMaxAttempts(10) .setMaxDuration(Duration.ofMinutes(5)); ctx.run("my-run", myRunRetryPolicy, () -> writeToOtherSystem()); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/RetryRunService.kt#here"} theme={null} val myRunRetryPolicy = retryPolicy { initialDelay = 5.seconds exponentiationFactor = 2.0f maxDelay = 60.seconds maxAttempts = 10 maxDuration = 5.minutes } ctx.runBlock("write", myRunRetryPolicy) { writeToOtherSystem() } ``` ```go Go {"CODE_LOAD::go/guides/retries.go#here"} theme={null} result, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return writeToOtherSystem() }, // After 10 seconds, give up retrying restate.WithMaxRetryDuration(time.Second*10), // On the first retry, wait 100 milliseconds before next attempt restate.WithInitialRetryInterval(time.Millisecond*100), // Grow retry interval with factor 2 restate.WithRetryIntervalFactor(2.0), ) if err != nil { return err } ``` Note that these retries are coordinated and initiated by the Restate Server. So the handler goes through the regular retry cycle of suspension and re-invocation. If you set a maximum number of attempts, then the run block will fail with a TerminalException once the retries are exhausted. Service-level retry policies are planned and will come soon. ## Application errors (terminal) By default, Restate infinitely retries all errors. In some cases, you might not want to retry an error (e.g. because of business logic, because the issue is not transient, ...). For these cases you can throw a terminal error. Terminal errors are permanent and are not retried by Restate. You can throw a terminal error as follows: ```ts TypeScript {"CODE_LOAD::ts/src/develop/error_handling.ts#terminal"} theme={null} throw new TerminalError("Something went wrong.", { errorCode: 500 }); ``` ```python Python {"CODE_LOAD::python/src/develop/error_handling.py#terminal"} theme={null} from restate.exceptions import TerminalError raise TerminalError("Something went wrong.") ``` ```java Java {"CODE_LOAD::java/src/main/java/develop/ErrorHandling.java#here"} theme={null} throw new TerminalException(500, "Something went wrong"); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/develop/ErrorHandling.kt#here"} theme={null} throw TerminalException(500, "Something went wrong") ``` ```go Go {"CODE_LOAD::go/develop/errorhandling.go#here"} theme={null} return restate.TerminalError(fmt.Errorf("Something went wrong."), 500) ``` ```rust Rust {"CODE_LOAD::rust/src/guides/retries.rs#terminal_error"} theme={null} Err(TerminalError::new("This is a terminal error")) ``` You can throw terminal errors from any place in your handler, including run blocks. Unless catched, terminal errors stop the execution and are propagated back to the caller. If the caller is another Restate service, the terminal error will propagate across RPCs, and will get thrown at the line where the RPC was made. If this is not caught, it will propagate further up the call stack until it reaches the original caller. You can catch terminal errors just like any other error, and build control flow around this. For example, the catch block can run undo actions for the actions you did earlier in your handler, to bring it to a consistent state before rethrowing the terminal error. For example, to catch a terminal error of a run block: ```ts TypeScript {"CODE_LOAD::ts/src/guides/retries.ts#catch"} theme={null} try { // Fails with a terminal error after 3 attempts or if the function throws one await ctx.run("write", () => writeToOtherSystem(), { maxRetryAttempts: 3, }); } catch (e) { if (e instanceof restate.TerminalError) { // Handle the terminal error: undo previous actions and // propagate the error back to the caller } throw e; } ``` ```python Python {"CODE_LOAD::python/src/guides/retries.py#catch"} theme={null} try: # Fails with a terminal error after 3 attempts or if the function throws one await ctx.run_typed("write", write_to_other_system, restate.RunOptions(max_attempts=3)) except TerminalError as err: # Handle the terminal error: undo previous actions and # propagate the error back to the caller raise err ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/RetryRunService.java#catch"} theme={null} try { // Fails with a terminal error after 3 attempts or if the function throws one ctx.run("my-run", RetryPolicy.defaultPolicy().setMaxAttempts(3), () -> writeToOtherSystem()); } catch (TerminalException e) { // Handle the terminal error: undo previous actions and // propagate the error back to the caller throw e; } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/RetryRunService.kt#catch"} theme={null} try { // Fails with a terminal error after 3 attempts or if the function throws one ctx.runBlock( "write", RetryPolicy( initialDelay = 500.milliseconds, maxAttempts = 3, exponentiationFactor = 2.0f)) { writeToOtherSystem() } } catch (e: TerminalException) { // Handle the terminal error: undo previous actions and // propagate the error back to the caller throw e } ``` ```go Go {"CODE_LOAD::go/guides/retries.go#catch"} theme={null} result, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return writeToOtherSystem() }) if err != nil { if restate.IsTerminalError(err) { // Handle the terminal error: undo previous actions and // propagate the error back to the caller } return err } ``` ```rust Rust {"CODE_LOAD::rust/src/guides/retries.rs#catch"} theme={null} // Fails with a terminal error after 3 attempts or if the function throws one if let Err(e) = ctx .run(|| write_to_other_system()) .retry_policy(RunRetryPolicy::default().max_attempts(3)) .await { // Handle the terminal error: undo previous actions and // propagate the error back to the caller return Err(e); } ``` When you throw a terminal error, you might need to undo the actions you did earlier in your handler to make sure that your system remains in a consistent state. Have a look at our [sagas guide](/guides/sagas) to learn more. ## Cancellations are Terminal Errors You can cancel invocations via the [CLI](/services/invocation/managing-invocations#cancel), UI and programmatically. When you cancel an invocation, it throws a terminal error in the handler processing the invocation the next time it awaits a Promise or Future of a Restate Context action (e.g. run block, RPC, sleep,...; `RestatePromise` in TypeScript, `DurableFuture` in Java). Unless caught, This terminal error will propagate up the call stack until it reaches the original caller. Here again, the handler needs to have [compensation logic](/guides/sagas) in place to make sure the system remains in a consistent state, when you cancel an invocation. ## Timeouts between Restate and the service There are two types of timeouts describing the behavior between Restate and the service. ### Inactivity timeout When the Restate Server does not receive a next journal entry from a running handler within the inactivity timeout, it will ask the handler to suspend. This timer guards against stalled service/handler invocations. Once it expires, Restate triggers a graceful termination by asking the service invocation to suspend (which preserves intermediate progress). By default, the inactivity timeout is set to one minute. You can increase the inactivity timeout if you have long-running `ctx.run` blocks, that lead to long pauses between journal entries. Otherwise, this timeout might kill the ongoing execution. ### Abort timeout This timer guards against stalled service/handler invocations that are supposed to terminate. The abort timeout is started after the 'inactivity timeout' has expired and the service/handler invocation has been asked to gracefully terminate. Once the timer expires, it will abort the service/handler invocation. By default, the abort timeout is set to one minute. This timer potentially interrupts user code. If the user code needs longer to gracefully terminate, then this value needs to be set accordingly. If you have long-running `ctx.run` blocks, you need to increase both timeouts to prevent the handler from terminating prematurely. ### Configuring the timeouts As with the retry policy, you can configure these timeouts on specific handlers, on all the handlers of a service, or globally in the Restate configuration directly. To configure these timeouts on a service/handler level, check [timeouts service configuration](/services/configuration#timeouts). Via the [`restate-server` configuration file](/server/configuration): ```toml restate.toml theme={null} [worker.invoker] inactivity-timeout = "1m" abort-timeout = "1m" ``` ```shell theme={null} restate-server --config-file restate.toml ``` Both timeouts follow the [jiff](https://docs.rs/jiff/latest/jiff/struct.Span.html) format. Or set it [via environment variables](/server/configuration#environment-variables), for example: ```shell theme={null} RESTATE_WORKER__INVOKER__INACTIVITY_TIMEOUT=5m \ RESTATE_WORKER__INVOKER__ABORT_TIMEOUT=5m \ restate-server ``` ## Common patterns These are some common patterns for handling errors in Restate: ### Sagas Have a look at the [sagas guide](/guides/sagas) to learn how to revert your system back to a consistent state after a terminal error. Keep track of compensating actions throughout your business logic and apply them in the catch block after a terminal error. ### Dead-letter queue A [dead-letter queue (DLQ)](https://aws.amazon.com/what-is/dead-letter-queue/) is a queue where you can send messages that could not be processed due to errors. You can implement this in Restate by wrapping your handler in a try-catch block. In the catch block you can forward the failed invocation to a DLQ Kafka topic or a catch-all handler which for example reports them or backs them up. Some errors might happen before the handler code gets invoked/starts running (e.g. service does not exist, request decoding errors in SDK HTTP server, ...). By default, Restate fails these requests with `400`. Handle these as follows: * In case the caller waited for the response of the failed call, the caller can handle the propagation to the DLQ. * If the caller did not wait for the response (one-way send), you would lose these messages. * Decoding errors can be caught by doing the decoding inside the handler. The called handler then takes raw input and does the decoding and validation itself. In this case, it would be included in the try-catch block which would do the dispatching: ```ts TypeScript {"CODE_LOAD::ts/src/guides/retries.ts#raw"} theme={null} myHandler: async (ctx: restate.Context) => { try { const rawRequest = ctx.request().body; const decodedRequest = decodeRequest(rawRequest); // ... rest of your business logic ... } catch (e) { if (e instanceof restate.TerminalError) { // Propagate to DLQ/catch-all handler } throw e; } }, ``` ```python Python {"CODE_LOAD::python/src/guides/retries.py#raw"} theme={null} @my_service.handler() async def my_handler(ctx: Context): try: raw_request = ctx.request().body decoded_request = decode_request(raw_request) # ... rest of your business logic ... except TerminalError as err: # Propagate to DLQ/catch-all handler raise err ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/RetryRunService.java#raw"} theme={null} @Handler public void myHandler(Context ctx, @Accept("*/*") @Raw byte[] request) { try { var decodedRequest = decodeRequest(request); // ... rest of your business logic ... } catch (TerminalException e) { // Propagate to DLQ/catch-all handler } } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/RetryRunService.kt#raw"} theme={null} @Handler suspend fun myHandler(ctx: Context, @Accept("*/*") @Raw request: ByteArray) { try { val decodedRequest = decodeRequest(request) // ... rest of your business logic ... } catch (e: TerminalException) { // Propagate to DLQ/catch-all handler throw e } } ``` ```go Go {"CODE_LOAD::go/guides/retries.go#raw"} theme={null} func (MyService) myHandler(ctx restate.Context) (string, error) { rawRequest := ctx.Request().Body decodedRequest, err := decodeRequest(rawRequest) if err != nil { if restate.IsTerminalError(err) { // Propagate to DLQ/catch-all handler } return "", err } // ... rest of your business logic ... return decodedRequest, nil } ``` ```rust Rust {"CODE_LOAD::rust/src/guides/retries.rs#raw"} theme={null} // Use Vec to represent a binary request async fn my_handler(&self, ctx: Context<'_>, request: Vec) -> Result<(), HandlerError> { let decoded_request = decode_request(&request)?; // ... rest of you business logic ... Ok(()) } ``` The other errors mainly occur due to misconfiguration of your setup (e.g. wrong service name, wrong handler name, forgot service registration...). You cannot handle those. ### Timeouts for context actions You can set timeouts for context actions like calls, awakeables, etc. to bound the time they take: ```ts TypeScript {"CODE_LOAD::ts/src/guides/retries.ts#timeout"} theme={null} try { // If the timeout hits first, it throws a `TimeoutError`. // If you do not catch it, it will lead to a retry. await ctx .serviceClient(myService) .myHandler("hello") .orTimeout({ seconds: 5 }); const { id, promise } = ctx.awakeable(); // do something that will trigger the awakeable await promise.orTimeout({ seconds: 5 }); } catch (e) { if (e instanceof restate.TimeoutError) { // Handle the timeout error } throw e; } ``` ```python Python {"CODE_LOAD::python/src/guides/retries.py#timeout"} theme={null} match await restate.select( greeting=ctx.service_call(my_service_handler, "value"), timeout=ctx.sleep(timedelta(seconds=5)), ): case ["greeting", greeting]: print("Greeting:", greeting) case ["timeout", _]: print("Timeout occurred") ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/RetryRunService.java#timeout"} theme={null} try { // If the timeout hits first, it throws a `TimeoutError`. // If you do not catch it, it will lead to a retry. MyServiceClient.fromContext(ctx).myHandler("Hello").await(Duration.ofSeconds(5)); var awakeable = ctx.awakeable(Boolean.class); // ...Do something that will trigger the awakeable awakeable.await(Duration.ofSeconds(5)); } catch (TimeoutException e) { // Handle the timeout error } ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/RetryRunService.kt#timeout"} theme={null} try { ctx.awakeable().withTimeout(5.seconds).await() } catch (e: TimeoutException) { // Handle the timeout } ``` ```go Go {"CODE_LOAD::go/guides/retries.go#timeout"} theme={null} awakeable := restate.Awakeable[string](ctx) timeout := restate.After(ctx, 5*time.Second) selector := restate.Select(ctx, awakeable, timeout) switch selector.Select() { case awakeable: result, err := awakeable.Result() if err != nil { return err } slog.Info("Awakeable resolved first with: " + result) case timeout: if err := timeout.Done(); err != nil { return err } slog.Info("Timeout hit first") } ``` # Guides Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/index Learn how to do common tasks with Restate. export const ProductCard = ({img, href, title, description, model, type}) => { return
{title}

{title}

{description}

; }; ## Recipes ## Development Guides ## Deployment Guides ## Integrations # Restate-Kafka Quickstart Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/kafka-quickstart Learn how to connect your Restate service to a Kafka topic. In this guide, you will learn how to connect your Restate service handler to a Kafka topic. Restate lets you connect any handler to a Kafka topic, and invoke the handler for each event that arrives on the topic. This way, you can use Restate to process events in a lightweight, flexible, transactional way. Get the Greeter service template and run the service}> You can choose any of the SDK languages for this quickstart. You do not need to adapt the handler code to be able to read from Kafka. Any handler can be connected to Kafka. Let's start a Kafka cluster with a single broker, and create a topic named `greetings`. You can run your Kafka cluster in your preferred way. Here, we will use Docker Compose to start a Kafka cluster. Create a `docker-compose.yaml` file with the following content: ```yaml docker-compose.yaml expandable theme={null} version: '3' services: broker: image: confluentinc/cp-kafka:7.5.0 container_name: broker ports: - "9092:9092" - "9101:9101" environment: KAFKA_BROKER_ID: 1 KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092 KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_PROCESS_ROLES: broker,controller KAFKA_NODE_ID: 1 KAFKA_CONTROLLER_QUORUM_VOTERS: 1@broker:29093 KAFKA_LISTENERS: PLAINTEXT://broker:29092,CONTROLLER://broker:29093,PLAINTEXT_HOST://0.0.0.0:9092 KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_LOG_DIRS: /tmp/kraft-combined-logs CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk init-kafka: image: confluentinc/cp-kafka:7.5.0 depends_on: - broker entrypoint: [ '/bin/sh', '-c' ] command: | "# blocks until kafka is reachable kafka-topics --bootstrap-server broker:29092 --list echo -e 'Creating kafka topics' kafka-topics --bootstrap-server broker:29092 --create --if-not-exists --topic greetings --replication-factor 1 --partitions 1 echo -e 'Successfully created the following topics:' kafka-topics --bootstrap-server broker:29092 --list" ``` Start the Kafka broker: ```shell theme={null} docker compose up ``` Now, let's start the Restate Server and let it know about the Kafka cluster via the configuration file. Store the following configuration in a file named `restate.toml`: ```toml restate.toml theme={null} [[ingress.kafka-clusters]] name = "my-cluster" brokers = ["PLAINTEXT://localhost:9092"] ``` Start the Restate Server from the same location as the configuration file: ```shell theme={null} restate-server --config-file restate.toml ``` Let the Restate Server know about the Greeter service by registering it: ```shell theme={null} restate deployments register localhost:9080 ``` Now, we need to make Restate subscribe to the Kafka topics and tell it where it should push the events that arrive on the topic. Execute the following curl command to create a subscription, and invoke the handler for each event: ```shell theme={null} curl localhost:9070/subscriptions --json '{ "source": "kafka://my-cluster/greetings", "sink": "service://Greeter/greet", "options": {"auto.offset.reset": "earliest"} }' ``` For Go, you need to capitalize the handler name: `service://Greeter/Greet`. This curl command calls the Admin API of the Restate Server and tells it to invoke the `greet` handler of the `Greeter` service for each event that arrives on the `greetings` topic in the `my-cluster` Kafka cluster. Create a Kafka producer and publish an event to the `greetings` topic: ```shell theme={null} docker exec -it broker kafka-console-producer --bootstrap-server broker:29092 --topic greetings ``` Then type a message and press enter. The greeter takes a String name as an input: ``` {"name":"Sarah"} ``` For some greeter templates, you might need to send just the name as `"Sarah"`. You now see your handler getting invoked. The way this worked is that Restate reads the message off the Kafka topic and durably persisted it, similar to what it does for HTTP invocations. It then **pushed** the message to the handler, as opposed to the pull mechanism that you would have with a Kafka consumer. If the handler fails, Restate will retry the request until it eventually succeeds. When invoking Basic Services, Restates ignores the key of the message. When invoking Virtual Objects, Restate uses the key of the Kafka message as the Virtual Object key. Whereas a Kafka partition contains multiple keys, Restate effectively keeps track of a queue per key. Have a look at the [Event Processing use case page](/use-cases/event-processing) to learn about what you can do with Restate and event processing. You can see the subscriptions that are active via the Admin API: ```shell theme={null} curl localhost:9070/subscriptions ``` Example output: ```json theme={null} { "subscriptions": [ { "id": "sub_11XHoawrCiWtv8kzhEyGtsR", "source": "kafka://my-cluster/my-topic", "sink": "service://Greeter/greet", "options": { "auto.offset.reset": "earliest", "client.id": "restate", "group.id": "sub_11XHoawrCiWtv8kzhEyGtsR" } } ] } ``` As you can see, subscriptions have an ID that starts with `sub_`. Now you can use the subscription ID to delete the subscription: ```shell theme={null} curl -X DELETE localhost:9070/subscriptions/sub_11XHoawrCiWtv8kzhEyGtsR ``` ## Related resources * [Event processing use cases](/use-cases/event-processing): Have a look at examples of how Restate gives you lightweight, transactional event processing. * Have a look at [the examples](https://github.com/restatedev/examples). There, you can find examples on Durable Execution and event processing. * [Reference docs for invoking over Kafka](/services/invocation/kafka) * [Blog post on Kafka integration](https://restate.dev/blog/restate--kafka-event-driven-apps-where-event-driven-is-an-implementation-detail/) # Scaling to Multi-Node Deployments Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/local-to-replicated Migrate a single node to a multi-node cluster. This guide shows how to scale an existing single-node deployment to a multi-node cluster. It assumes you have a running single-node Restate server that is running the replicated loglet and replicated metadata server, which are enabled by default in Restate >= v1.4.0. Older versions of Restate ({'<='} v1.3.2) use the local loglet and local metadata server by default. The local loglet and local metadata server are suitable for development and single-node deployments. We recommend using the replicated loglet and replicated metadata server to ensure high availability and durability. They are also required for multi-node clusters. Starting with version v1.4.0, existing logs and metadata will be automatically migrated to the replicated equivalents. Make sure to [upgrade](/server/upgrading) your single-node deployment to the latest Restate version before adding more nodes. Check that the metadata service is running using the [`restatectl`](/server/clusters#controlling-clusters-with-restatectl) tool. ```shell theme={null} restatectl metadata-server list-servers ``` You should see a single member node providing metadata service: ``` Metadata service NODE STATUS VERSION LEADER MEMBERS APPLIED COMMITTED TERM LOG-LENGTH SNAP-INDEX SNAP-SIZE N1 Member v1 N1 [N1] 2 2 2 1 1 6.7 KiB ``` If you see the node as unreachable with an error reason of "Unimplemented", verify that you are running the latest version. The older local metadata server is no longer supported in Restate v1.4.0 and newer. The default configuration is cluster-ready. However, if you have explicitly specified server roles in configuration, you should make sure these include the `log-server` role. Similarly, if you have explicitly set the loglet provider to be `local`, you should remove this. While the local loglet is still supported, the default type is `replicated` starting from v1.4.0. If you have a configuration file and would like to make these settings explicit, it should contain the following: ```toml restate.toml theme={null} roles = [ "worker", "admin", "metadata-server", "log-server", # needed for replicated loglet "http-ingress", ] [bifrost] default-provider = "replicated" # default ``` Confirm that cluster configuration uses the replicated loglet as the default log provider. ```shell theme={null} restatectl config get ``` In the default configuration you should expect to see: ``` ⚙️ Cluster Configuration ├ Number of partitions: 24 ├ Partition replication: {node: 1} └ Logs Provider: replicated ├ Log replication: {node: 1} └ Nodeset size: 1 ``` You can confirm that the type of logs in use by the server using the command: ```shell theme={null} restatectl logs list ``` If you have enabled the `log-server` role and left the default provider unset (or set it to `replicated`), and still do not see the cluster configuration you can change the cluster log configuration using: ```shell theme={null} restatectl config set --log-provider replicated --log-replication 1 ``` This command sets the default log provider to `replicated` with a default replication of `1`. As long as you have a single-node deployment, you must set the replication to `1`. Otherwise, the server will become unavailable because it cannot provision the new log segments. If you plan to extend your single-node deployment to a multi-node deployment, you also need to [configure the snapshot repository](/server/snapshots#configuring-automatic-snapshotting). This allows new nodes to join the cluster by restoring the latest snapshot. ```toml restate.toml theme={null} [worker.snapshots] destination = "s3://snapshots-bucket/cluster-prefix" ``` For other nodes to join, you need to snapshot every partition because the local loglet is not accessible from other nodes. Run the following command to create a snapshot for each partition. ```shell theme={null} restatectl snapshots create --trim-log ``` Note that this also instructs Restate to trim the logs after partition state has been successfully published to the snapshot repository. This ensures that the logs no longer reference historic local loglets that may have existed on the original node. To add more nodes to your cluster, you need to start new Restate servers with the same `cluster-name` and a metadata client with the address of the existing node. ```toml restate.toml theme={null} cluster-name = "my-cluster" [metadata-client] addresses = ["http://metadata-node.cluster:5122"] ``` Metadata is critical to the operation of your cluster and we recommend that you run the `metadata-server` role on additional nodes. Make the cluster metadata service resilient to node failures by specifying the full list of metadata servers on all cluster nodes. ```toml restate.toml theme={null} [metadata-client] addresses = ["http://node1.cluster:5122", "http://node2.cluster:5122", "http://node3.cluster:5122"] ``` If everything is set up correctly, you should see the new nodes in the cluster status. ```shell theme={null} restatectl status ``` ## See also * Try [growing your cluster](/server/clusters#growing-the-cluster) to tolerate node failures # Parallelizing Work Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/parallelizing-work Execute a list of tasks in parallel and then gather their result. This guide shows how to use the Restate to **execute a list of tasks in parallel and then gather their result**, also known as fan-out, fan-in. ## How does Restate help? * Restate lets you schedule the tasks asynchronously and guarantees that all tasks will run, with **retries and recovery** on failures. * Restate **turns Promises/Futures into durable, distributed constructs** that are persisted in Restate and can be recovered and awaited on another process. * You can deploy the subtask executors on **serverless** infrastructure, like AWS Lambda, to let them scale automatically. The main task, that is idle while waiting on the subtasks, gets suspended until it can make progress. **Fan out**: You can fan out tasks with Restate by creating a handler that processes a single subtask, and then scheduling it repeatedly from another handler. **Fan in**: You can fan in the results of the subtasks by using Restate's Promise Combinators to wait for all promises to resolve. ## Example The example implements a worker service: 1. It splits a task into subtasks. 2. It schedules all the subtasks. Each subtask results in a promise that gets added to a list. 3. The result is gathered by waiting for all promises to resolve. You can run this on FaaS infrastructure, like AWS Lambda, and it will scale automatically. The `run` handler will then suspend while it waits for all subtasks to finish. Restate will then resume the handler when all subtasks are done. ```ts TypeScript {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/parallelizework/fan_out_worker.ts?collapse_prequel"} theme={null} const fanOutWorker = restate.service({ name: "worker", handlers: { run: async (ctx: Context, task: Task): Promise => { // Split the task in subtasks const subtasks: SubTask[] = await ctx.run("split task", () => split(task)); // Fan out the subtasks - run them in parallel const resultPromises = []; for (const subtask of subtasks) { const subResultPromise = ctx.serviceClient(fanOutWorker).runSubtask(subtask); resultPromises.push(subResultPromise); } // Fan in - Aggregate the results const results = await RestatePromise.all(resultPromises); return aggregate(ctx, results); }, // Can also run on FaaS runSubtask: async (ctx: Context, subtask: SubTask): Promise => { // Processing logic goes here ... // Can be moved to a separate service to scale independently return executeSubtask(ctx, subtask); }, }, }); restate.serve({ services: [fanOutWorker], port: 9080, }); ``` ```java Java {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/java/patterns-use-cases/src/main/java/my/example/parallelizework/FanOutWorker.java?collapse_prequel"} theme={null} @Service public class FanOutWorker { @Handler public Result run(Context ctx, Task task) { // Split the task in subtasks var subTasks = ctx.run(new TypeRef<>() {}, () -> split(task)); // Fan out the subtasks - run them in parallel List> resultFutures = new ArrayList<>(); for (SubTask subTask : subTasks) { resultFutures.add(FanOutWorkerClient.fromContext(ctx).runSubtask(subTask)); } DurableFuture.all(resultFutures).await(); // Fan in - Aggregate the results var results = resultFutures.stream().map(future -> (SubTaskResult) future.await()).toList(); return aggregate(results); } // Can also run on FaaS @Handler public SubTaskResult runSubtask(Context ctx, SubTask subTask) { // Processing logic goes here ... // Can be moved to a separate service to scale independently return executeSubtask(ctx, subTask); } public static void main(String[] args) { RestateHttpServer.listen(Endpoint.bind(new FanOutWorker())); } } ``` ```kotlin Kotlin {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/kotlin/patterns-use-cases/src/main/kotlin/my/example/parallelizework/FanOutWorker.kt?collapse_prequel"} theme={null} @Service class FanOutWorker { @Handler suspend fun run(ctx: Context, task: Task): TaskResult { // Split the task in subtasks val subTasks = ctx.runBlock { task.split() } // Fan out the subtasks - run them in parallel // Fan in - Await all results and aggregate val results = subTasks.map { FanOutWorkerClient.fromContext(ctx).runSubtask(it) }.awaitAll() return results.aggregate() } @Handler suspend fun runSubtask(ctx: Context, subTask: SubTask): SubTaskResult { // Processing logic goes here ... // Can be moved to a separate service to scale independently return subTask.execute(ctx) } } fun main() { RestateHttpServer.listen(endpoint { bind(FanOutWorker()) }) } ``` ```python Python {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/python/patterns-use-cases/parallelizework/app.py?collapse_prequel"} theme={null} fan_out_worker = restate.Service("FanOutWorker") @fan_out_worker.handler() async def run(ctx: restate.Context, task: Task) -> Result: # Split the task in subtasks subtasks = await ctx.run_typed("split task", split, task=task) # Fan out the subtasks - run them in parallel result_promises = [ ctx.run_typed(f"execute {subtask}", execute_subtask, subtask=subtask) for subtask in subtasks.subtasks ] # Fan in - Aggregate the results results_done = await restate.gather(*result_promises) results = [await result for result in results_done] return aggregate(results) app = restate.app([fan_out_worker]) if __name__ == "__main__": import hypercorn import asyncio conf = hypercorn.Config() conf.bind = ["0.0.0.0:9080"] asyncio.run(hypercorn.asyncio.serve(app, conf)) ``` ```go Go {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/parallelizework/fanoutworker.go?collapse_prequel"} theme={null} type FanOutWorker struct{} func (FanOutWorker) Run(ctx restate.Context, task Task) (Result, error) { // Split the task into subtasks subtasks, err := split(task) if err != nil { return Result{}, err } // Fan out the subtasks - run them in parallel subtaskFutures := make([]restate.Selectable, 0, len(subtasks)) for _, subtask := range subtasks { subtaskFutures = append(subtaskFutures, restate.Service[SubTaskResult](ctx, "FanOutWorker", "RunSubtask").RequestFuture(subtask)) } selector := restate.Select(ctx, subtaskFutures...) // Fan in - Aggregate the results subResults := make([]SubTaskResult, 0, len(subtasks)) for selector.Remaining() { response, err := selector.Select().(restate.ResponseFuture[SubTaskResult]).Response() if err != nil { return Result{}, err } subResults = append(subResults, response) } // Fan in - Aggregate the results return aggregate(subResults) } // RunSubtask can also run on FaaS func (FanOutWorker) RunSubtask(ctx restate.Context, subtask SubTask) (SubTaskResult, error) { // Processing logic goes here ... // Can be moved to a separate service to scale independently return executeSubtask(ctx, subtask) } func main() { server := server.NewRestate(). Bind(restate.Reflect(FanOutWorker{})) if err := server.Start(context.Background(), ":9080"); err != nil { slog.Error("application exited unexpectedly", "err", err.Error()) os.Exit(1) } } ``` In this example, we parallelize RPC calls, but this can also be used to parallelize `ctx.run` actions. This pattern is implementable with any of our SDKs. We are still working on translating all patterns to all SDK languages. If you need help with a specific language, please reach out to us via [Discord](https://discord.com/invite/skW3AZ6uGd) or [Slack](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA). ## Running the example ```shell TypeScript theme={null} restate example typescript-patterns-use-cases && cd typescript-patterns-use-cases ``` ```shell Java theme={null} restate example java-patterns-use-cases && cd java-patterns-use-cases ``` ```shell Kotlin theme={null} restate example kotlin-patterns-use-cases && cd kotlin-patterns-use-cases ``` ```shell Python theme={null} restate example python-patterns-use-cases && cd python-patterns-use-cases ``` ```shell Go theme={null} restate example go-patterns-use-cases && cd go-patterns-use-cases ``` ```shell theme={null} restate-server ``` ```shell TypeScript theme={null} npm install npx tsx watch ./src/parallelizework/fan_out_worker.ts ``` ```shell Java theme={null} ./gradlew -PmainClass=my.example.parallelizework.FanOutWorker run ``` ```shell Kotlin theme={null} ./gradlew -PmainClass=my.example.parallelizework.FanOutWorkerKt run ``` ```shell Python theme={null} python parallelizework/app.py ``` ```shell Go theme={null} go run ./src/parallelizework ``` ```shell theme={null} restate deployments register localhost:9080 ``` ```shell TypeScript theme={null} curl localhost:8080/worker/run \ --json '{"description": "get out of bed,shower,make coffee,have breakfast"}' ``` ```shell Java theme={null} curl localhost:8080/FanOutWorker/run \ --json '{"description": "get out of bed,shower,make coffee,have breakfast"}' ``` ```shell Kotlin theme={null} curl localhost:8080/FanOutWorker/run \ --json '{"description": "get out of bed,shower,make coffee,have breakfast"}' ``` ```shell Python theme={null} curl localhost:8080/FanOutWorker/run \ --json '{"description": "get out of bed,shower,make coffee,have breakfast"}' ``` ```shell Go theme={null} curl localhost:8080/FanOutWorker/Run \ --json '{"description": "get out of bed,shower,make coffee,have breakfast"}' ``` See how all tasks get spawned in parallel, finish at different times, and then get aggregated. ```shell TypeScript theme={null} [restate] [worker/runSubtask][inv_17jBqoqRG0TN3msVqHEpZn2aQMOX5kSKrf][2025-01-17T08:51:44.993Z] INFO: Started executing subtask: get out of bed [restate] [worker/runSubtask][inv_1f8R1NuF0LF27EdQ0R6s7PR8hld245OM8h][2025-01-17T08:51:44.995Z] INFO: Started executing subtask: shower [restate] [worker/runSubtask][inv_101oPhGwxQqZ0sQebkQnpGyV9Rp3oj9CSJ][2025-01-17T08:51:44.997Z] INFO: Started executing subtask: make coffee [restate] [worker/runSubtask][inv_1eKDShaxMCEB6DXasrR5OtRXJEvA2je33X][2025-01-17T08:51:44.998Z] INFO: Started executing subtask: have breakfast [restate] [worker/runSubtask][inv_17jBqoqRG0TN3msVqHEpZn2aQMOX5kSKrf][2025-01-17T08:51:47.003Z] INFO: Execution subtask finished: get out of bed [restate] [worker/runSubtask][inv_101oPhGwxQqZ0sQebkQnpGyV9Rp3oj9CSJ][2025-01-17T08:51:48.007Z] INFO: Execution subtask finished: make coffee [restate] [worker/runSubtask][inv_1f8R1NuF0LF27EdQ0R6s7PR8hld245OM8h][2025-01-17T08:51:48.999Z] INFO: Execution subtask finished: shower [restate] [worker/runSubtask][inv_1eKDShaxMCEB6DXasrR5OtRXJEvA2je33X][2025-01-17T08:51:49.001Z] INFO: Execution subtask finished: have breakfast [restate] [worker/run][inv_18QHSeAYfvim1oNXRl9I5105veQcTW3BEl][2025-01-17T08:51:49.007Z] INFO: Aggregated result: get out of bed: DONE,shower: DONE,make coffee: DONE,have breakfast: DONE ``` ```shell Java theme={null} 2025-01-17 10:00:58 INFO [FanOutWorker/run][inv_1jNoSMJtWluo4Ir43OUyDAxD9weMAQ4OeR] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_1kdpBvVXdqyo3saU6KThul6Jgkfot6LcRP] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_1kdpBvVXdqyo3saU6KThul6Jgkfot6LcRP] my.example.parallelizework.utils.Utils - Started executing subtask: get out of bed 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_162MCD5ertQ65pdG0uDIYRMgLBFYZkNPnb] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_162MCD5ertQ65pdG0uDIYRMgLBFYZkNPnb] my.example.parallelizework.utils.Utils - Started executing subtask: shower 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_10bPiFTjBUXX35qtzOTPNr0vfgoYYVehpf] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_10bPiFTjBUXX35qtzOTPNr0vfgoYYVehpf] my.example.parallelizework.utils.Utils - Started executing subtask: make coffee 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_1115lzidXq7M7CtLZn0aEyUvMC4zkXYLWF] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-01-17 10:00:58 INFO [FanOutWorker/runSubtask][inv_1115lzidXq7M7CtLZn0aEyUvMC4zkXYLWF] my.example.parallelizework.utils.Utils - Started executing subtask: have breakfast 2025-01-17 10:00:59 INFO [FanOutWorker/runSubtask][inv_162MCD5ertQ65pdG0uDIYRMgLBFYZkNPnb] my.example.parallelizework.utils.Utils - Execution subtask finished: shower 2025-01-17 10:00:59 INFO [FanOutWorker/runSubtask][inv_162MCD5ertQ65pdG0uDIYRMgLBFYZkNPnb] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-01-17 10:01:00 INFO [FanOutWorker/runSubtask][inv_1kdpBvVXdqyo3saU6KThul6Jgkfot6LcRP] my.example.parallelizework.utils.Utils - Execution subtask finished: get out of bed 2025-01-17 10:01:00 INFO [FanOutWorker/runSubtask][inv_1kdpBvVXdqyo3saU6KThul6Jgkfot6LcRP] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-01-17 10:01:04 INFO [FanOutWorker/runSubtask][inv_10bPiFTjBUXX35qtzOTPNr0vfgoYYVehpf] my.example.parallelizework.utils.Utils - Execution subtask finished: make coffee 2025-01-17 10:01:04 INFO [FanOutWorker/runSubtask][inv_10bPiFTjBUXX35qtzOTPNr0vfgoYYVehpf] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-01-17 10:01:05 INFO [FanOutWorker/runSubtask][inv_1115lzidXq7M7CtLZn0aEyUvMC4zkXYLWF] my.example.parallelizework.utils.Utils - Execution subtask finished: have breakfast 2025-01-17 10:01:05 INFO [FanOutWorker/runSubtask][inv_1115lzidXq7M7CtLZn0aEyUvMC4zkXYLWF] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-01-17 10:01:05 INFO [FanOutWorker/run][inv_1jNoSMJtWluo4Ir43OUyDAxD9weMAQ4OeR] my.example.parallelizework.utils.Utils - Aggregated result: get out of bed: DONE, shower: DONE, make coffee: DONE, have breakfast: DONE 2025-01-17 10:01:05 INFO [FanOutWorker/run][inv_1jNoSMJtWluo4Ir43OUyDAxD9weMAQ4OeR] dev.restate.sdk.core.InvocationStateMachine - End invocation ``` ```shell Kotlin theme={null} 2025-03-06 12:20:18 INFO [FanOutWorker/run][inv_1fGEUyfogPKK5cbSCSWzpCkcDpkQIKSzMB] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_146fBfVLISKb2sCWqesf6uMReXdRKvqmv7] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_146fBfVLISKb2sCWqesf6uMReXdRKvqmv7] FanOutWorker - Started executing subtask: get out of bed 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_18T9WW6paOhm6eciCeBt5iqHXRY4h2NRvP] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_18T9WW6paOhm6eciCeBt5iqHXRY4h2NRvP] FanOutWorker - Started executing subtask: shower 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_10kE3b5UcL8L64ghFpHQjeeAEowKNis4dH] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_10kE3b5UcL8L64ghFpHQjeeAEowKNis4dH] FanOutWorker - Started executing subtask: make coffee 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_1fCFmQ9ulbxL2MBwYCDRgV8Was8PDBedW1] dev.restate.sdk.core.InvocationStateMachine - Start invocation 2025-03-06 12:20:18 INFO [FanOutWorker/runSubtask][inv_1fCFmQ9ulbxL2MBwYCDRgV8Was8PDBedW1] FanOutWorker - Started executing subtask: have breakfast 2025-03-06 12:20:21 INFO [FanOutWorker/runSubtask][inv_10kE3b5UcL8L64ghFpHQjeeAEowKNis4dH] FanOutWorker - Execution subtask finished: make coffee 2025-03-06 12:20:21 INFO [FanOutWorker/runSubtask][inv_10kE3b5UcL8L64ghFpHQjeeAEowKNis4dH] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-03-06 12:20:24 INFO [FanOutWorker/runSubtask][inv_146fBfVLISKb2sCWqesf6uMReXdRKvqmv7] FanOutWorker - Execution subtask finished: get out of bed 2025-03-06 12:20:24 INFO [FanOutWorker/runSubtask][inv_146fBfVLISKb2sCWqesf6uMReXdRKvqmv7] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-03-06 12:20:25 INFO [FanOutWorker/runSubtask][inv_1fCFmQ9ulbxL2MBwYCDRgV8Was8PDBedW1] FanOutWorker - Execution subtask finished: have breakfast 2025-03-06 12:20:25 INFO [FanOutWorker/runSubtask][inv_1fCFmQ9ulbxL2MBwYCDRgV8Was8PDBedW1] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-03-06 12:20:27 INFO [FanOutWorker/runSubtask][inv_18T9WW6paOhm6eciCeBt5iqHXRY4h2NRvP] FanOutWorker - Execution subtask finished: shower 2025-03-06 12:20:27 INFO [FanOutWorker/runSubtask][inv_18T9WW6paOhm6eciCeBt5iqHXRY4h2NRvP] dev.restate.sdk.core.InvocationStateMachine - End invocation 2025-03-06 12:20:27 INFO [FanOutWorker/run][inv_1fGEUyfogPKK5cbSCSWzpCkcDpkQIKSzMB] FanOutWorker - Aggregated result: get out of bed: DONE, shower: DONE, make coffee: DONE, have breakfast: DONE 2025-03-06 12:20:27 INFO [FanOutWorker/run][inv_1fGEUyfogPKK5cbSCSWzpCkcDpkQIKSzMB] dev.restate.sdk.core.InvocationStateMachine - End invocation ``` ```shell Python theme={null} [2025-01-17 12:00:05,183] [12245] [INFO] - Started executing subtask: get out of bed [2025-01-17 12:00:05,184] [12247] [INFO] - Started executing subtask: shower [2025-01-17 12:00:05,184] [12245] [INFO] - Started executing subtask: make coffee [2025-01-17 12:00:05,185] [12245] [INFO] - Started executing subtask: have breakfast [2025-01-17 12:00:05,188] [12245] [INFO] - Execution subtask finished: make coffee [2025-01-17 12:00:08,193] [12245] [INFO] - Execution subtask finished: get out of bed [2025-01-17 12:00:10,194] [12247] [INFO] - Execution subtask finished: shower [2025-01-17 12:00:15,196] [12245] [INFO] - Execution subtask finished: have breakfast [2025-01-17 12:00:15,198] [12245] [INFO] - Aggregated result: get out of bed: DONE,shower: DONE,make coffee: DONE,have breakfast: DONE ``` ```shell Go theme={null} 2025/01/16 16:41:22 INFO Handling invocation method=FanOutWorker/Run invocationID=inv_1lkcVTBmCorR3fSPhE0pNTiO8XFXoV34C5 2025/01/16 16:41:22 INFO Handling invocation method=FanOutWorker/RunSubtask invocationID=inv_1jpZWOrDK45b2ZwWCapl68GgXzNfoOh0BP 2025/01/16 16:41:22 Started executing subtask: get out of bed 2025/01/16 16:41:22 INFO Handling invocation method=FanOutWorker/RunSubtask invocationID=inv_10eVGnmjP1ET4PgI3z82rvXcVlCnSOep3P 2025/01/16 16:41:22 Started executing subtask: shower 2025/01/16 16:41:22 INFO Handling invocation method=FanOutWorker/RunSubtask invocationID=inv_1i3RduoDMNnb4ideAtWaWCLRDaIO62eghP 2025/01/16 16:41:22 Started executing subtask: make coffee 2025/01/16 16:41:22 INFO Handling invocation method=FanOutWorker/RunSubtask invocationID=inv_142WnXnWDxfy6k4JanZ7DQVqAL6zmktuxP 2025/01/16 16:41:22 Started executing subtask: have breakfast 2025/01/16 16:41:24 Execution subtask finished: get out of bed 2025/01/16 16:41:24 INFO Invocation completed successfully method=FanOutWorker/RunSubtask invocationID=inv_1jpZWOrDK45b2ZwWCapl68GgXzNfoOh0BP 2025/01/16 16:41:25 Execution subtask finished: shower 2025/01/16 16:41:25 INFO Invocation completed successfully method=FanOutWorker/RunSubtask invocationID=inv_10eVGnmjP1ET4PgI3z82rvXcVlCnSOep3P 2025/01/16 16:41:25 Execution subtask finished: have breakfast 2025/01/16 16:41:25 INFO Invocation completed successfully method=FanOutWorker/RunSubtask invocationID=inv_142WnXnWDxfy6k4JanZ7DQVqAL6zmktuxP 2025/01/16 16:41:26 Execution subtask finished: make coffee 2025/01/16 16:41:26 INFO Invocation completed successfully method=FanOutWorker/RunSubtask invocationID=inv_1i3RduoDMNnb4ideAtWaWCLRDaIO62eghP 2025/01/16 16:41:26 Aggregated result: get out of bed: DONE,shower: DONE,have breakfast: DONE,make coffee: DONE 2025/01/16 16:41:26 INFO Invocation completed successfully method=FanOutWorker/Run invocationID=inv_1lkcVTBmCorR3fSPhE0pNTiO8XFXoV34C5 ``` ## Related resources * Concurrent tasks docs: [TS](/develop/ts/concurrent-tasks) / [Java/Kotlin](/develop/java/concurrent-tasks) / [Python](/develop/python/concurrent-tasks) / [Go](/develop/go/concurrent-tasks) / [Rust](https://docs.rs/restate-sdk/latest/restate_sdk/macro.select.html) # Rate Limiting Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/rate-limiting Control request rates and prevent service overload with Restate Rate limiting is a technique used to control the number of requests or operations that a service can handle within a specific time period. It helps prevent system overload and ensures fair resource usage. ## How does Restate help? Restate provides several features that make it well-suited for implementing rate limiting: * **Durable state**: Store and manage rate limit counters reliably. * **Virtual objects**: Isolated rate limiters per key (user, API endpoint, etc.). * **Durable timers**: Schedule token refills and cleanup operations. Restate doesn't have built-in rate limiting functionality, but its building blocks make it easy to build this. ## Example This implementation provides a token bucket rate limiter that can control the rate of operations for any service or resource. You can copy the following files to your project: The **limiter client interface**, which you can use in your services: ```ts TypeScript expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/ratelimit/limiter_client.ts"} theme={null} import { Context, TerminalError } from "@restatedev/restate-sdk"; import type { Limiter as LimiterObject, Reservation as ReservationResponse } from "./limiter"; export interface Reservation extends ReservationResponse { // cancel indicates that the reservation holder will not perform the reserved action // and reverses the effects of this Reservation on the rate limit as much as possible, // considering that other reservations may have already been made. cancel(): void; } export interface Limiter { // limit returns the maximum overall event rate. limit(): Promise; // burst returns the maximum burst size. Burst is the maximum number of tokens // that can be consumed in a single call to allow, reserve, or wait, so higher // Burst values allow more events to happen at once. // A zero Burst allows no events, unless limit == Inf. burst(): Promise; // tokens returns the number of tokens available at time t (defaults to now). tokens(): Promise; // allow reports whether n events may happen at time t. // Use this method if you intend to drop / skip events that exceed the rate limit. // Otherwise use reserve or wait. allow(n?: number): Promise; // reserve returns a Reservation that indicates how long the caller must wait before n events happen. // The limiter takes this Reservation into account when allowing future events. // The returned Reservation’s ok parameter is false if n exceeds the limiter's burst size, or provided waitLimitMillis. // Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events. // If you need to cancel the delay, use wait instead. // To drop or skip events exceeding rate limit, use allow instead. reserve(n?: number, waitLimitMillis?: number): Promise; // setLimit sets a new limit for the limiter. The new limit, and burst, may be violated // or underutilized by those which reserved (using reserve or wait) but did not yet act // before setLimit was called. setLimit(newLimit: number): Promise; // setBurst sets a new burst size for the limiter. setBurst(newBurst: number): Promise; // setRate sets a new limit and burst size for the limiter. setRate(newLimit: number, newBurst: number): Promise; // waitN blocks until the limiter permits n events to happen. // It returns an error if n exceeds the limiter's burst size, the invocation is canceled, // or the wait would be longer than the deadline. // The burst limit is ignored if the rate limit is Inf. wait(n?: number, waitLimitMillis?: number): Promise; } export namespace Limiter { export function fromContext(ctx: Context, limiterID: string): Limiter { const client = ctx.objectClient({ name: "limiter" }, limiterID); return { async limit() { return (await client.state()).limit; }, async burst() { return (await client.state()).burst; }, async tokens() { return client.tokens(); }, async allow(n?: number) { const r = await client.reserve({ n, waitLimitMillis: 0, }); return r.ok; }, async reserve(n?: number, waitLimitMillis?: number) { const r = await client.reserve({ n, waitLimitMillis, }); return { cancel() { ctx .objectSendClient({ name: "limiter" }, limiterID) .cancelReservation(r); }, ...r, }; }, async setLimit(newLimit: number) { return client.setRate({ newLimit, }); }, async setBurst(newBurst: number) { return client.setRate({ newBurst, }); }, async setRate(newLimit: number, newBurst: number) { return client.setRate({ newLimit, newBurst, }); }, async wait(n: number = 1, waitLimitMillis?: number) { // Reserve const r = await this.reserve(n, waitLimitMillis); if (!r.ok) { if (waitLimitMillis === undefined) { throw new TerminalError(`rate: Wait(n=${n}) would exceed the limiters burst`, { errorCode: 429, }); } else { throw new TerminalError( `rate: Wait(n=${n}) would either exceed the limiters burst or the provided waitLimitMillis`, { errorCode: 429 }, ); } } // Wait if necessary const delay = delayFrom(r, r.creationDate); if (delay == 0) { return; } try { await ctx.sleep(delay); } catch (e) { // this only happens on invocation cancellation - cancel the reservation in the background r.cancel(); throw e; } }, }; } } // delayFrom returns the duration in millis for which the reservation holder must wait // before taking the reserved action. Zero duration means act immediately. // Infinity means the limiter cannot grant the tokens requested in this // Reservation within the maximum wait time. function delayFrom(r: ReservationResponse, date: number): number { if (!r.ok) { return Infinity; } const delay = r.dateToAct - date; if (delay < 0) { return 0; } return Math.floor(delay); } ``` ```go Go expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/ratelimit/client/limitclient.go"} theme={null} package client import ( "time" "github.com/restatedev/examples/go/patterns-use-cases/src/ratelimit/types" restate "github.com/restatedev/sdk-go" ) type Limiter struct { ctx restate.Context limiterID string } func NewLimiter(ctx restate.Context, limiterID string) *Limiter { return &Limiter{ ctx: ctx, limiterID: limiterID, } } func (lim *Limiter) state() (types.LimiterState, error) { return restate.Object[types.LimiterState](lim.ctx, "Limiter", lim.limiterID, "State").Request(restate.Void{}) } // Limit returns the maximum overall event rate. func (lim *Limiter) Limit() (types.Limit, error) { state, err := lim.state() if err != nil { return 0.0, err } return state.Limit, nil } // Burst returns the maximum burst size. Burst is the maximum number of tokens // that can be consumed in a single call to Allow, Reserve, or Wait, so higher // Burst values allow more events to happen at once. // A zero Burst allows no events, unless limit == Inf. func (lim *Limiter) Burst() (int, error) { state, err := lim.state() if err != nil { return 0, err } return state.Burst, nil } // Tokens returns the number of tokens available now. func (lim *Limiter) Tokens() (float64, error) { return restate.Object[float64](lim.ctx, "Limiter", lim.limiterID, "Tokens").Request(restate.Void{}) } // Allow reports whether an event may happen now. func (lim *Limiter) Allow() (bool, error) { return lim.AllowN(1) } // AllowN reports whether n events may happen now. // Use this method if you intend to drop / skip events that exceed the rate limit. // Otherwise use Reserve or Wait. func (lim *Limiter) AllowN(n int) (bool, error) { r, err := restate.Object[types.Reservation](lim.ctx, "Limiter", lim.limiterID, "ReserveN").Request(types.ReserveRequest{ N: n, MaxFutureReserve: 0, }) if err != nil { return false, err } return r.Ok, nil } type Reservation struct { lim *Limiter r types.Reservation } // Cancel indicates that the reservation holder will not perform the reserved action // and reverses the effects of this Reservation on the rate limit as much as possible, // considering that other reservations may have already been made. func (r *Reservation) Cancel() { restate.ObjectSend(r.lim.ctx, "Limiter", r.lim.limiterID, "CancelReservation").Send(r.r) } // Reserve is shorthand for ReserveN(1). func (lim *Limiter) Reserve() (*Reservation, error) { return lim.ReserveN(1) } // ReserveN returns a Reservation that indicates how long the caller must wait before n events happen. // The Limiter takes this Reservation into account when allowing future events. // The returned Reservation’s OK() method returns false if n exceeds the Limiter's burst size. // Usage example: // // r := lim.ReserveN(1) // if !r.OK() { // // Not allowed to act! Did you remember to set lim.burst to be > 0 ? // return // } // restate.Sleep(r.Delay()) // Act() // // Use this method if you wish to wait and slow down in accordance with the rate limit without dropping events. // If you need to respect a deadline or cancel the delay, use Wait instead. // To drop or skip events exceeding rate limit, use Allow instead. func (lim *Limiter) ReserveN(n int) (*Reservation, error) { return lim.reserveN(n, types.InfDuration) } func (lim *Limiter) reserveN(n int, maxFutureReserve time.Duration) (*Reservation, error) { r, err := restate.Object[types.Reservation](lim.ctx, "Limiter", lim.limiterID, "ReserveN").Request(types.ReserveRequest{ N: n, MaxFutureReserve: maxFutureReserve, }) if err != nil { return nil, err } return &Reservation{ lim: lim, r: r, }, nil } // Wait is shorthand for WaitN(1, types.InfDuration). func (lim *Limiter) Wait() (err error) { return lim.WaitN(1, types.InfDuration) } // SetLimit sets a new Limit for the limiter. The new Limit, and Burst, may be violated // or underutilized by those which reserved (using Reserve or Wait) but did not yet act // before SetLimit was called. func (lim *Limiter) SetLimit(limit types.Limit) error { _, err := restate.Object[types.Reservation](lim.ctx, "Limiter", lim.limiterID, "SetRate").Request(types.SetRateRequest{ Limit: &limit, }) return err } // SetBurst sets a new burst size for the limiter. func (lim *Limiter) SetBurst(burst int) error { _, err := restate.Object[types.Reservation](lim.ctx, "Limiter", lim.limiterID, "SetRate").Request(types.SetRateRequest{ Burst: &burst, }) return err } // SetRate is a convenience method to call both SetLimit and SetBurst atomically. func (lim *Limiter) SetRate(limit types.Limit, burst int) error { _, err := restate.Object[types.Reservation](lim.ctx, "Limiter", lim.limiterID, "SetRate").Request(types.SetRateRequest{ Limit: &limit, Burst: &burst, }) return err } // WaitN blocks until lim permits n events to happen. // It returns an error if n exceeds the Limiter's burst size, the invocation is // canceled, or the expected wait time exceeds the maxFutureReserve // The burst limit is ignored if the rate limit is Inf. func (lim *Limiter) WaitN(n int, maxFutureReserve time.Duration) (err error) { r, err := lim.reserveN(n, maxFutureReserve) if err != nil { return err } if !r.r.Ok { if maxFutureReserve == types.InfDuration { return restate.WithErrorCode(restate.TerminalErrorf("rate: Wait(n=%d) would exceed the limiters burst", n), 429) } else { return restate.WithErrorCode(restate.TerminalErrorf("rate: Wait(n=%d) would either exceed the limiters burst or the provided maxFutureReserve", n), 429) } } // Wait if necessary delay := r.DelayFrom(r.r.CreationTime) if delay == 0 { return } if err := restate.Sleep(lim.ctx, delay); err != nil { // this only happens on invocation cancellation - cancel the reservation in the background r.Cancel() return err } return nil } // Delay is shorthand for DelayFrom(time.Now()) using a deterministic timestamp. func (r *Reservation) Delay() time.Duration { t, _ := restate.Run(r.lim.ctx, func(ctx restate.RunContext) (time.Time, error) { return time.Now(), nil }) return r.DelayFrom(t) } // DelayFrom returns the duration for which the reservation holder must wait // before taking the reserved action. Zero duration means act immediately. // InfDuration means the limiter cannot grant the tokens requested in this // Reservation within the maximum wait time. func (r *Reservation) DelayFrom(t time.Time) time.Duration { if !r.r.Ok { return types.InfDuration } delay := r.r.TimeToAct.Sub(t) if delay < 0 { return 0 } return delay } ``` The **limiter implementation**, which manages the token bucket state and logic: ```ts TypeScript expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/ratelimit/limiter.ts"} theme={null} // a faithful reimplementation of https://pkg.go.dev/golang.org/x/time/rate#Limiter // using virtual object state import { object, ObjectContext } from "@restatedev/restate-sdk"; type LimiterState = { state: LimiterStateInner; }; type LimiterStateInner = { limit: number; burst: number; tokens: number; // last is the last time the limiter's tokens field was updated, in unix millis last: number; // lastEvent is the latest time of a rate-limited event (past or future), in unix millis lastEvent: number; }; export interface Reservation { ok: boolean; tokens: number; creationDate: number; dateToAct: number; // This is the Limit at reservation time, it can change later. limit: number; } export const limiter = object({ name: "limiter", handlers: { state: async (ctx: ObjectContext): Promise => { return getState(ctx); }, tokens: async (ctx: ObjectContext): Promise => { // deterministic date not needed, as there is only an output entry const tokens = advance(await getState(ctx), Date.now()); return tokens; }, reserve: async ( ctx: ObjectContext, { n = 1, waitLimitMillis = Infinity }: { n?: number; waitLimitMillis?: number }, ): Promise => { let lim = await getState(ctx); if (lim.limit == Infinity) { // deterministic date is not necessary, as this is part of a response body, which won't be replayed. const now = Date.now(); return { ok: true, tokens: n, creationDate: now, dateToAct: now, limit: 0, }; } let r: Reservation; ({ lim, r } = await ctx.run(() => { const now = Date.now(); let tokens = advance(lim, now); // Calculate the remaining number of tokens resulting from the request. tokens -= n; // Calculate the wait duration let waitDurationMillis = 0; if (tokens < 0) { waitDurationMillis = durationFromTokens(lim.limit, -tokens); } // Decide result const ok = n <= lim.burst && waitDurationMillis <= waitLimitMillis; // Prepare reservation const r = { ok, tokens: 0, creationDate: now, dateToAct: 0, limit: lim.limit, } satisfies Reservation; if (ok) { r.tokens = n; r.dateToAct = now + waitDurationMillis; // Update state lim.last = now; lim.tokens = tokens; lim.lastEvent = r.dateToAct; } return { lim, r }; })); setState(ctx, lim); return r; }, setRate: async ( ctx: ObjectContext, { newLimit, newBurst }: { newLimit?: number; newBurst?: number }, ) => { if (newLimit === undefined && newBurst === undefined) { return; } let lim = await getState(ctx); lim = await ctx.run(() => { const now = Date.now(); const tokens = advance(lim, now); lim.last = now; lim.tokens = tokens; if (newLimit !== undefined) lim.limit = newLimit; if (newBurst !== undefined) lim.burst = newBurst; return lim; }); setState(ctx, lim); }, cancelReservation: async (ctx: ObjectContext, r: Reservation) => { let lim = await getState(ctx); lim = await ctx.run(() => { const now = Date.now(); if (lim.limit == Infinity || r.tokens == 0 || r.dateToAct < now) { return lim; } // calculate tokens to restore // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved // after r was obtained. These tokens should not be restored. const restoreTokens = r.tokens - tokensFromDuration(r.limit, lim.lastEvent - r.dateToAct); if (restoreTokens <= 0) { return lim; } // advance time to now let tokens = advance(lim, now); // calculate new number of tokens tokens += restoreTokens; if (tokens > lim.burst) { tokens = lim.burst; } // update state lim.last = now; lim.tokens = tokens; if (r.dateToAct == lim.lastEvent) { const prevEvent = r.dateToAct + durationFromTokens(r.limit, -r.tokens); if (prevEvent >= now) { lim.lastEvent = prevEvent; } } return lim; }); setState(ctx, lim); }, }, }); function advance(lim: LimiterStateInner, date: number): number { let last = lim.last; if (date <= last) { last = date; } // Calculate the new number of tokens, due to time that passed. const elapsedMillis = date - last; const delta = tokensFromDuration(lim.limit, elapsedMillis); let tokens = lim.tokens + delta; if (tokens > lim.burst) { tokens = lim.burst; } return tokens; } async function getState(ctx: ObjectContext): Promise { return ( (await ctx.get("state")) ?? { limit: 0, burst: 0, tokens: 0, last: 0, lastEvent: 0, } ); } async function setState(ctx: ObjectContext, lim: LimiterStateInner) { ctx.set("state", lim); } function durationFromTokens(limit: number, tokens: number): number { if (limit <= 0) { return Infinity; } return (tokens / limit) * 1000; } function tokensFromDuration(limit: number, durationMillis: number): number { if (limit <= 0) { return 0; } return (durationMillis / 1000) * limit; } export type Limiter = typeof limiter; ``` ```go Go expandable {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/ratelimit/service/limiter.go"} theme={null} package service import ( "math" "time" "github.com/restatedev/examples/go/patterns-use-cases/src/ratelimit/types" restate "github.com/restatedev/sdk-go" ) type Limiter struct{} func (Limiter) State(ctx restate.ObjectContext) (types.LimiterState, error) { return restate.Get[types.LimiterState](ctx, "state") } func (Limiter) Tokens(ctx restate.ObjectContext) (float64, error) { lim, err := restate.Get[types.LimiterState](ctx, "state") if err != nil { return 0.0, err } // deterministic date not needed, as there is only an output entry tokens := advance(&lim, time.Now()) return tokens, nil } func (Limiter) ReserveN(ctx restate.ObjectContext, req types.ReserveRequest) (types.Reservation, error) { lim, err := restate.Get[types.LimiterState](ctx, "state") if err != nil { return types.Reservation{}, err } if lim.Limit == types.Inf { // deterministic date is not necessary, as this is part of a response body, which won't be replayed. t := time.Now() return types.Reservation{ Ok: true, CreationTime: t, Tokens: req.N, TimeToAct: t, }, nil } type runResult struct { types.LimiterState `json:"limiterState"` types.Reservation `json:"reservation"` } result, err := restate.Run(ctx, func(ctx restate.RunContext) (runResult, error) { t := time.Now() tokens := advance(&lim, t) // Calculate the remaining number of tokens resulting from the request. tokens -= float64(req.N) // Calculate the wait duration var waitDuration time.Duration if tokens < 0 { waitDuration = durationFromTokens(lim.Limit, -tokens) } // Decide result ok := req.N <= lim.Burst && waitDuration <= req.MaxFutureReserve // Prepare reservation r := types.Reservation{ Ok: ok, CreationTime: t, Limit: lim.Limit, } if ok { r.Tokens = req.N r.TimeToAct = t.Add(waitDuration) // Update state lim.Last = t lim.Tokens = tokens lim.LastEvent = r.TimeToAct } return runResult{lim, r}, nil }) if err != nil { return types.Reservation{}, err } restate.Set(ctx, "state", result.LimiterState) return result.Reservation, nil } func (Limiter) SetRate(ctx restate.ObjectContext, req types.SetRateRequest) error { if req.Limit == nil && req.Burst == nil { return nil } lim, err := restate.Get[types.LimiterState](ctx, "state") if err != nil { return err } lim, err = restate.Run(ctx, func(ctx restate.RunContext) (types.LimiterState, error) { t := time.Now() tokens := advance(&lim, t) lim.Last = t lim.Tokens = tokens if req.Limit != nil { lim.Limit = *req.Limit } if req.Burst != nil { lim.Burst = *req.Burst } return lim, nil }) if err != nil { return err } restate.Set(ctx, "state", lim) return nil } func (Limiter) CancelReservation(ctx restate.ObjectContext, r types.Reservation) error { lim, err := restate.Get[types.LimiterState](ctx, "state") if err != nil { return err } lim, err = restate.Run(ctx, func(ctx restate.RunContext) (types.LimiterState, error) { t := time.Now() if r.Limit == types.Inf || r.Tokens == 0 || r.TimeToAct.Before(t) { return lim, nil } // calculate tokens to restore // The duration between lim.lastEvent and r.timeToAct tells us how many tokens were reserved // after r was obtained. These tokens should not be restored. restoreTokens := float64(r.Tokens) - tokensFromDuration(r.Limit, lim.LastEvent.Sub(r.TimeToAct)) if restoreTokens <= 0 { return lim, nil } // advance time to now tokens := advance(&lim, t) // calculate new number of tokens tokens += restoreTokens if burst := float64(lim.Burst); tokens > burst { tokens = burst } // update state lim.Last = t lim.Tokens = tokens if r.TimeToAct == lim.LastEvent { prevEvent := r.TimeToAct.Add(durationFromTokens(r.Limit, float64(-r.Tokens))) if !prevEvent.Before(t) { lim.LastEvent = prevEvent } } return lim, nil }) restate.Set(ctx, "state", lim) return nil } func advance(lim *types.LimiterState, t time.Time) float64 { last := lim.Last if t.Before(last) { last = t } // Calculate the new number of tokens, due to time that passed. elapsed := t.Sub(last) delta := tokensFromDuration(lim.Limit, elapsed) tokens := lim.Tokens + delta if burst := float64(lim.Burst); tokens > burst { tokens = burst } return tokens } // durationFromTokens is a unit conversion function from the number of tokens to the duration // of time it takes to accumulate them at a rate of limit tokens per second. func durationFromTokens(limit types.Limit, tokens float64) time.Duration { if limit <= 0 { return types.InfDuration } duration := (tokens / float64(limit)) * float64(time.Second) // Cap the duration to the maximum representable int64 value, to avoid overflow. if duration > float64(math.MaxInt64) { return types.InfDuration } return time.Duration(duration) } // tokensFromDuration is a unit conversion function from a time duration to the number of tokens // which could be accumulated during that duration at a rate of limit tokens per second. func tokensFromDuration(limit types.Limit, d time.Duration) float64 { if limit <= 0 { return 0 } return d.Seconds() * float64(limit) } ``` This implementation provides a `RateLimiter` Virtual Object which implements the **Token Bucket Algorithm**: * Tokens are added at a specified rate (limit) * Tokens are consumed when operations are performed * A burst capacity allows for short bursts of activity **Key Methods** (via the client interface): * `limit()` / `burst()`: Get maximum event rate and burst size * `tokens()`: Get number of available tokens * `wait()`: Block until events are permitted to happen * `setRate()` / `setLimit()` / `setBurst()`: Configure rate limiting parameters ## Usage Example Here's how to use the rate limiter in your services: ```ts TypeScript {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/ratelimit/service.ts"} theme={null} import { Context, service } from "@restatedev/restate-sdk"; import { Limiter } from "./limiter_client"; const LIMITER_NAME = "myService-expensiveMethod"; export const myService = service({ name: "myService", handlers: { expensiveMethod: async (ctx: Context) => { const limiter = Limiter.fromContext(ctx, LIMITER_NAME); await limiter.wait(); console.log("expensive!"); }, expensiveMethodBatch: async (ctx: Context, batchSize: number = 20) => { const limiter = Limiter.fromContext(ctx, LIMITER_NAME); await limiter.wait(batchSize); for (let i = 0; i < batchSize; i++) { console.log("expensive!"); } }, }, }); export type MyService = typeof myService; ``` ```go Go {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/ratelimit/example/main.go?collapse_prequel"} theme={null} type LimitedTask struct{} func (LimitedTask) RunTask(ctx restate.Context) error { limiter := client.NewLimiter(ctx, "LimitedTask-RunTask") if err := limiter.Wait(); err != nil { return err } // Implement doing the work... return nil } func main() { server := server.NewRestate(). Bind(restate.Reflect(service.Limiter{})). Bind(restate.Reflect(LimitedTask{})) if err := server.Start(context.Background(), ":9080"); err != nil { slog.Error("application exited unexpectedly", "err", err.Error()) os.Exit(1) } } ``` ## Running the example ```bash TypeScript theme={null} restate example typescript-patterns-use-cases && cd typescript-patterns-use-cases ``` ```bash Go theme={null} restate example go-patterns-use-cases && cd go-patterns-use-cases ``` ```bash theme={null} restate-server ``` ```bash TypeScript theme={null} npm install npx tsx watch ./src/ratelimit/app.ts ``` ```bash Go theme={null} go run ./src/ratelimit/example/main.go ``` ```bash theme={null} restate deployments register localhost:9080 ``` Set up the limiter named `myService-expensiveMethod` with a rate limit of 1 per second: ```bash TypeScript theme={null} curl localhost:8080/limiter/myService-expensiveMethod/setRate \ --json '{"newLimit": 1, "newBurst": 1}' ``` ```bash Go theme={null} curl localhost:8080/Limiter/LimitedTask-RunTask/SetRate \ --json '{"limit": 1, "burst": 1}' ``` Try sending multiple requests quickly to see the rate limiting in action. You can send requests that are subject to the limiter like this: ```bash TypeScript theme={null} # send one request curl localhost:8080/myService/expensiveMethod # send lots for i in $(seq 1 30); do curl localhost:8080/myService/expensiveMethod && echo "request completed"; done ``` ```bash Go theme={null} # send one request curl localhost:8080/LimitedTask/RunTask # send lots for i in $(seq 1 30); do curl localhost:8080/LimitedTask/RunTask && echo "request completed"; done ``` You should observe that only one request is processed per second. You can then try changing the limit or the burst and sending more requests. In the Restate UI, you can observe: * Invocations getting scheduled one per second in the Invocations tab * Rate limiter state and token counts in the State tab # Request Lifecycle Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/request-lifecycle Deep dive into the lifecycle of a request in Restate This guide provides a detailed technical walkthrough of how Restate processes requests from start to finish, including the mechanisms that enable durable execution and failure recovery. ## Request Processing Lifecycle When Restate receives a request to invoke a handler, it follows this detailed process: ### 1. Persist Request Restate persists the request and creates a new execution journal for this invocation. From this point, Restate guarantees that it will process the request to completion, even in the face of failures. ### 2. Forward to Service Restate opens a bidirectional streaming connection (HTTP/2) to the service for which the request is meant. This connection will be used to stream messages back and forth between the Server and the SDK. It then forwards the request to the service. ### 3. Invoke Handler The Restate SDK, embedded in the service, calls the handler with the request data and a context object that provides access to Restate features like state management, service calls, and timers. ### 4. Track Execution As the handler runs, it uses the Restate Context to call other services, update state, or perform side effects. The Restate SDK under-the-hood sends a message to the Restate server for each Context operation. The Restate Server then records it in the execution journal. ### 5. Handle Failures If the handler crashes or fails, Restate either loses the connection or receives an error message. In this case, the Restate Server will send a retry request to the service, including the latest journal. Failure detection mechanisms: * Connection between Server and service dropped * Explicit error messages (not terminal errors) * Inactivity timeouts reached ### 6. Replay Journal The service receives the retry request and restarts the execution of the handler. Whenever the handler performs an action on the Restate context (like calling another service, updating state, or setting a timer), the SDK checks the journal for the last recorded result. If it finds a previous result, it skips executing that action and uses the recorded result instead. This way, the handler resumes from exactly where it left off. ### 7. Complete Execution Once the handler completes successfully, the Restate SDK proxies the result back to the client via the Restate Server. The server then marks the invocation as complete in the journal. ## Cross-service interactions Note that inter-handler communication between two durable handlers goes through Restate (not direct service-to-service). The caller appends an invocation event to its journal, which Restate Server turns into an invocation for the target service, providing end-to-end idempotency. ## Request-response Deployment Targets Some deployment targets don't support bidirectional streaming connections (HTTP/2), such as AWS Lambda. In these cases, the Restate SDK uses a request-response pattern (HTTP/1.1) instead. In this case, the SDK will try to make as much progress as possible. When it hits a point where it needs to wait for a response (like a service call, sleep, or run action), it will send the new journal entries to the Restate Server and suspend the execution. The server will send a retry request once the response is available. Execution then resumes by replaying the journal up to the suspension point. # Sagas Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/sagas Implementing undo operations in case of failures, to keep your system consistent A **Saga** is a design pattern for handling transactions that span multiple services. It breaks the process into a sequence of local operations, each with a corresponding **compensating action**. If a failure occurs partway through, these compensations are triggered to **undo** completed steps, ensuring your system stays consistent even when things go wrong. ## How does Restate help? Restate makes implementing resilient sagas simple: * **Durable Execution**: Restate guarantees completion and automatically retries from failure points. No manual state tracking or retry logic needed * **Code-first approach**: Define sagas using regular code, no DSLs required Sagas UI ## Example A travel booking workflow: book a flight, rent a car, then book a hotel. If any step fails (e.g. hotel full), we roll back previous steps to maintain consistency. Sagas example diagram **Implementation:** * Wrap business logic in a try-block, throw terminal errors for compensation cases * Add compensations to a list for each step * In catch block, run compensations in reverse order and rethrow Note: Golang uses `defer` for compensations. ```ts TypeScript {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/typescript/patterns-use-cases/src/sagas/booking_workflow.ts?collapse_prequel?remove_comments"} theme={null} const bookingWorkflow = restate.service({ name: "BookingWorkflow", handlers: { run: async (ctx: restate.Context, req: BookingRequest) => { const { customerId, flight, car, hotel } = req; const compensations = []; try { compensations.push(() => ctx.run("Cancel flight", () => flightClient.cancel(customerId))); await ctx.run("Book flight", () => flightClient.book(customerId, flight)); compensations.push(() => ctx.run("Cancel car", () => carRentalClient.cancel(customerId))); await ctx.run("Book car", () => carRentalClient.book(customerId, car)); compensations.push(() => ctx.run("Cancel hotel", () => hotelClient.cancel(customerId))); await ctx.run("Book hotel", () => hotelClient.book(customerId, hotel)); } catch (e) { if (e instanceof restate.TerminalError) { for (const compensation of compensations.reverse()) { await compensation(); } } throw e; } }, }, }); restate.serve({ services: [bookingWorkflow], port: 9080, }); ``` ```java Java {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java?collapse_prequel?remove_comments"} theme={null} @Service public class BookingWorkflow { public record BookingRequest( String customerId, FlightRequest flight, CarRequest car, HotelRequest hotel) {} @Handler public void run(Context ctx, BookingRequest req) throws TerminalException { List compensations = new ArrayList<>(); try { compensations.add(() -> ctx.run("Cancel flight", () -> FlightClient.cancel(req.customerId))); ctx.run("Book flight", () -> FlightClient.book(req.customerId, req.flight())); compensations.add(() -> ctx.run("Cancel car", () -> CarRentalClient.cancel(req.customerId))); ctx.run("Book car", () -> CarRentalClient.book(req.customerId, req.car())); compensations.add(() -> ctx.run("Cancel hotel", () -> HotelClient.cancel(req.customerId))); ctx.run("Book hotel", () -> HotelClient.book(req.customerId, req.hotel())); } catch (TerminalException e) { for (Runnable compensation : compensations) { compensation.run(); } throw e; } } public static void main(String[] args) { RestateHttpServer.listen(Endpoint.bind(new BookingWorkflow())); } } ``` ```kotlin Kotlin {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt?collapse_prequel?remove_comments"} theme={null} @Service class BookingWorkflow { @Handler suspend fun run(ctx: Context, req: BookingRequest) { val compensations = mutableListOf Unit>() try { compensations.add { ctx.runBlock("Cancel flight") { cancelFlight(req.customerId) } } ctx.runBlock("Book flight") { bookFlight(req.customerId, req.flight) } compensations.add { ctx.runBlock("Cancel car") { cancelCar(req.customerId) } } ctx.runBlock("Book car") { bookCar(req.customerId, req.car) } compensations.add { ctx.runBlock("Cancel hotel") { cancelHotel(req.customerId) } } ctx.runBlock("Book hotel") { bookHotel(req.customerId, req.hotel) } } catch (e: TerminalException) { compensations.reversed().forEach { it() } throw e } } } fun main() { RestateHttpServer.listen(endpoint { bind(BookingWorkflow()) }) } ``` ```python Python {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/python/patterns-use-cases/sagas/app.py?collapse_prequel?remove_comments"} theme={null} booking_workflow = restate.Service("BookingWorkflow") @booking_workflow.handler() async def run(ctx: restate.Context, req: BookingRequest): compensations = [] try: compensations.append( lambda: ctx.run_typed("Cancel flight", flight_client.cancel, customer_id=req.customer_id) ) await ctx.run_typed("Book flight", flight_client.book, customer_id=req.customer_id, flight=req.flight) compensations.append( lambda: ctx.run_typed("Cancel car", car_rental_client.cancel, customer_id=req.customer_id) ) await ctx.run_typed("Book car", car_rental_client.book, customer_id=req.customer_id, car=req.car) compensations.append( lambda: ctx.run_typed("Cancel hotel", hotel_client.cancel, customer_id=req.customer_id) ) await ctx.run_typed("Book hotel", hotel_client.book, customer_id=req.customer_id, hotel=req.hotel) except TerminalError as e: for compensation in reversed(compensations): await compensation() raise e app = restate.app([booking_workflow]) if __name__ == "__main__": import hypercorn import asyncio conf = hypercorn.Config() conf.bind = ["0.0.0.0:9080"] asyncio.run(hypercorn.asyncio.serve(app, conf)) ``` ```go Go {"CODE_LOAD::https://raw.githubusercontent.com/restatedev/examples/refs/heads/main/go/patterns-use-cases/src/sagas/bookingworkflow.go?collapse_prequel?remove_comments"} theme={null} type BookingWorkflow struct{} func (BookingWorkflow) Run(ctx restate.Context, req BookingRequest) (err error) { var compensations []func() (restate.Void, error) defer func() { if err != nil { for _, compensation := range slices.Backward(compensations) { if _, compErr := compensation(); compErr != nil { err = compErr } } } }() compensations = append(compensations, func() (restate.Void, error) { return restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return CancelFlight(req.CustomerId) }, restate.WithName("Cancel flight"), ) }) if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return BookFlight(req.CustomerId, req.Flight) }, restate.WithName("Book flight"), ); err != nil { return err } compensations = append(compensations, func() (restate.Void, error) { return restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return CancelCar(req.CustomerId) }, restate.WithName("Cancel car"), ) }) if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return BookCar(req.CustomerId, req.Car) }, restate.WithName("Book car"), ); err != nil { return err } compensations = append(compensations, func() (restate.Void, error) { return restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return CancelHotel(req.CustomerId) }, restate.WithName("Cancel hotel"), ) }) if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return BookHotel(req.CustomerId, req.Hotel) }, restate.WithName("Book hotel"), ); err != nil { return err } return nil } func main() { if err := server.NewRestate(). Bind(restate.Reflect(BookingWorkflow{})). Start(context.Background(), ":9080"); err != nil { log.Fatal(err) } } ``` View on GitHub: [TS](https://github.com/restatedev/examples/blob/main/typescript/patterns-use-cases/src/sagas/booking_workflow.ts) / [Java](https://github.com/restatedev/examples/blob/main/java/patterns-use-cases/src/main/java/my/example/sagas/BookingWorkflow.java) / [Kotlin](https://github.com/restatedev/examples/blob/main/kotlin/patterns-use-cases/src/main/kotlin/my/example/sagas/BookingWorkflow.kt) / [Python](https://github.com/restatedev/examples/blob/main/python/patterns-use-cases/sagas/app.py) / [Go](https://github.com/restatedev/examples/blob/main/go/patterns-use-cases/src/sagas/bookingworkflow.go) This pattern is implementable with any of our SDKs. We are still working on translating all patterns to all SDK languages. If you need help with a specific language, please reach out to us via [Discord](https://discord.com/invite/skW3AZ6uGd) or [Slack](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA). ## When to use Sagas Restate automatically retries transient failures (network hiccups, temporary outages). For non-transient failures, sagas are essential: 1. **Business logic failures**: When failures are business decisions (e.g. "Hotel is full"), retrying won't help. Throw a terminal error to trigger compensations. 2. **User/system cancellations**: When you [cancel](/services/invocation/managing-invocations#cancel) long-running invocations, sagas undo previous operations to maintain consistency. ## Running the example ```bash TypeScript theme={null} restate example typescript-patterns-use-cases && cd typescript-patterns-use-cases ``` ```bash Java theme={null} restate example java-patterns-use-cases && cd java-patterns-use-cases ``` ```bash Kotlin theme={null} restate example kotlin-patterns-use-cases && cd kotlin-patterns-use-cases ``` ```bash Python theme={null} restate example python-patterns-use-cases && cd python-patterns-use-cases ``` ```bash Go theme={null} restate example go-patterns-use-cases && cd go-patterns-use-cases ``` ```bash theme={null} restate-server ``` ```bash TypeScript theme={null} npm install npx tsx watch ./src/sagas/booking_workflow.ts ``` ```bash Java theme={null} ./gradlew -PmainClass=my.example.sagas.BookingWorkflow run ``` ```bash Kotlin theme={null} ./gradlew -PmainClass=my.example.sagas.BookingWorkflowKt run ``` ```bash Python theme={null} python sagas/app.py ``` ```bash Go theme={null} go run ./src/sagas ``` ```bash theme={null} restate deployments register localhost:9080 ``` ```bash TypeScript theme={null} curl localhost:8080/BookingWorkflow/run --json '{ "flight": { "flightId": "12345", "passengerName": "John Doe" }, "car": { "pickupLocation": "Airport", "rentalDate": "2024-12-16" }, "hotel": { "arrivalDate": "2024-12-16", "departureDate": "2024-12-20" } }' ``` ```bash Java theme={null} curl localhost:8080/BookingWorkflow/run --json '{ "flight": { "flightId": "12345", "passengerName": "John Doe" }, "car": { "pickupLocation": "Airport", "rentalDate": "2024-12-16" }, "hotel": { "arrivalDate": "2024-12-16", "departureDate": "2024-12-20" } }' ``` ```bash Kotlin theme={null} curl localhost:8080/BookingWorkflow/run --json '{ "flight": { "flightId": "12345", "passengerName": "John Doe" }, "car": { "pickupLocation": "Airport", "rentalDate": "2024-12-16" }, "hotel": { "arrivalDate": "2024-12-16", "departureDate": "2024-12-20" } }' ``` ```bash Python theme={null} curl localhost:8080/BookingWorkflow/run --json '{ "flight": { "flightId": "12345", "passengerName": "John Doe" }, "car": { "pickupLocation": "Airport", "rentalDate": "2024-12-16" }, "hotel": { "arrivalDate": "2024-12-16", "departureDate": "2024-12-20" } }' ``` ```bash Go theme={null} curl localhost:8080/BookingWorkflow/Run --json '{ "flight": { "flightId": "12345", "passengerName": "John Doe" }, "car": { "pickupLocation": "Airport", "rentalDate": "2024-12-16" }, "hotel": { "arrivalDate": "2024-12-16", "departureDate": "2024-12-20" } }' ``` See in the Restate UI (`localhost:9070`) how all steps were executed, and how the compensations were triggered because the hotel was full. Sagas UI ## Advanced: Idempotency and compensations Sagas in Restate are flexible and powerful since they're implemented in user code. However, you need to make sure compensations are idempotent. The example uses customer ID for idempotency, preventing duplicate bookings on retries. The API provider deduplicates requests based on this ID. Different APIs require different approaches: 1. **Two-phase APIs**: First *reserve*, then *confirm* or *cancel*. Register the compensation after reservation, when you have the resource ID. This type of API usually auto-cancels reservations after a timeout. ```ts TypeScript {"CODE_LOAD::ts/src/guides/sagas/booking_workflow.ts#twostep"} theme={null} const bookingId = await ctx.run(() => flightClient.reserve(customerId, flight) ); compensations.push(() => ctx.run(() => flightClient.cancel(bookingId))); // ... do other work, like reserving a car, etc. ... await ctx.run(() => flightClient.confirm(bookingId)); ``` ```python Python {"CODE_LOAD::python/src/guides/sagas/app.py#twostep"} theme={null} booking_id = await ctx.run_typed( "reserve", flight_client.reserve, customer_id=customer_id, flight=flight ) compensations.append( lambda: ctx.run_typed("cancel", flight_client.cancel, booking_id=booking_id) ) # ... do other work, like reserving a car, etc. ... await ctx.run_typed("confirm", flight_client.confirm, booking_id=booking_id) ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/sagas/BookingWorkflow.java#twostep"} theme={null} String bookingId = ctx.run(String.class, () -> FlightClient.reserve(flight)); compensations.add(() -> ctx.run(() -> FlightClient.cancel(bookingId))); // ... do other work, like reserving a car, etc. ... ctx.run(() -> FlightClient.confirm(bookingId)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/sagas/BookingWorkflow.kt#twostep"} theme={null} // For each action, we register a compensation that will be executed on failures val bookingId = ctx.runBlock { reserveFlight(customerId, flight) } compensations.add { ctx.runBlock { cancelFlight(bookingId) } } // ... do other work, like reserving a car, etc. ... compensations.add { ctx.runBlock { confirmFlight(bookingId) } } ``` ```go Go {"CODE_LOAD::go/guides/sagas/bookingworkflow.go#twostep"} theme={null} bookingId, err := restate.Run(ctx, func(ctx restate.RunContext) (string, error) { return ReserveFlight(req.CustomerId, req.Flight) }) if err != nil { return err } compensations = append(compensations, func() (restate.Void, error) { return restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return CancelFlight(bookingId) }) }) // ... do other work, like reserving a car, etc. ... if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return ConfirmFlight(bookingId) }); err != nil { return err } ``` 2. **One-shot APIs with idempotency key**: Generate idempotency key, persist in Restate, register compensation (e.g. `refund`), then do action (e.g. `charge`). Register compensation first in case action succeeded but confirmation was lost. ```ts TypeScript {"CODE_LOAD::ts/src/guides/sagas/booking_workflow.ts#idempotency"} theme={null} const paymentId = ctx.rand.uuidv4(); compensations.push(() => ctx.run(() => paymentClient.refund(paymentId))); await ctx.run(() => paymentClient.charge(paymentInfo, paymentId)); ``` ```python Python {"CODE_LOAD::python/src/guides/sagas/app.py#idempotency"} theme={null} payment_id = ctx.uuid() compensations.append(lambda: ctx.run_typed("refund", payment.refund, payment_id=payment_id)) await ctx.run_typed("charge", payment.charge, payment_info=payment_info, payment_id=payment_id) ``` ```java Java {"CODE_LOAD::java/src/main/java/guides/sagas/BookingWorkflow.java#idempotency"} theme={null} String paymentId = ctx.random().nextUUID().toString(); compensations.add(() -> ctx.run(() -> PaymentClient.refund(paymentId))); ctx.run(() -> PaymentClient.charge(paymentInfo, paymentId)); ``` ```kotlin Kotlin {"CODE_LOAD::kotlin/src/main/kotlin/guides/sagas/BookingWorkflow.kt#idempotency"} theme={null} val paymentId = ctx.random().nextUUID().toString() compensations.add { ctx.runBlock { refund(paymentId) } } ctx.runBlock { charge(paymentInfo, paymentId) } ``` ```go Go {"CODE_LOAD::go/guides/sagas/bookingworkflow.go#idempotency"} theme={null} paymentID := restate.Rand(ctx).UUID().String() // Register the refund as a compensation, using the idempotency key compensations = append(compensations, func() (restate.Void, error) { return restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return Refund(paymentID) }) }) // Do the payment using the idempotency key if _, err = restate.Run(ctx, func(ctx restate.RunContext) (restate.Void, error) { return Charge(paymentID, req.PaymentInfo) }); err != nil { return err } ``` ## Related resources * [Error Handling guide](/guides/error-handling) * [Cancellation of invocations](/services/invocation/managing-invocations#cancel) * [Blog post: Graceful cancellations: How to keep your application and workflow state consistent 💪](https://restate.dev/blog/graceful-cancellations-how-to-keep-your-application-and-workflow-state-consistent/) # XState Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/guides/xstate Integrate Restate with XState to implement durable state machines. Restate integrates with [XState](https://xstate.js.org/). Have a look at the [repo](https://github.com/restatedev/xstate) to learn more. # Welcome to Restate! Source: https://restate-6d46e1dc-update-go-sdk.mintlify.app/index Build innately resilient backends and AI agents Restate is a lightweight runtime to turn AI agents, workflows, and backend services into durable processes. Focus on your logic, not failure mechanics. Write normal code and let Restate handles resilience and consistency automatically. ## Key capabilities **Durable execution**: Code automatically stores completed steps and resumes from where it left off when recovering from failures. **Built-in state**: Maintain state beyond workflow executions and share it between functions with strong consistency guarantees. **Reliable communication**: Call services sync or async with guaranteed execution and exactly-once semantics. **Time-based coordination**: Sleep, schedule, and wait for external events with durable timers. **Workflows**: Coordinate long-running processes, human approvals, listen to webhooks and other signals. ## Common use cases Manage stateful AI agents with reliable tool usage and long-running conversations. Build approval processes, multi-step operations, and business workflows that survive failures. Coordinate calls across multiple services with automatic retries and failure handling. Process events with exactly-once guarantees and automatic retry handling. ## First time here? Build your first Restate service in minutes. Understand the core building blocks. Learn how to build common applications with Restate: [AI agents](/tour/vercel-ai-agents) • [Workflows](/tour/workflows) • [Microservice orchestration](/tour/microservice-orchestration) Chat with our AI assistant to get answers to your questions about Restate. ## Learning resources A collection of examples that illustrate how to use Restate to solve common application challenges. Learn how to do common tasks with Restate: patterns, integrations, deployment tutorials, ... ## Reference Implement Restate applications in one of the available SDKs.
[TypeScript](/develop/ts/services) • [Java](/develop/java/services) • [Kotlin](/develop/java/services) • [Python](/develop/python/services) • [Go](/develop/go/services) • [Rust](https://docs.rs/restate-sdk/latest/restate_sdk/)
Deploy and operate services on your preferred platform. [Deploy](/deploy/services/kubernetes) • [Invoke](/services/invocation/http) • [Versioning](/services/versioning) • [Monitor & Inspect](/services/introspection) Get started immediately with Restate Cloud, or host your own Restate server.
## Community Join the Restate Discord or Slack communities.
[](https://discord.gg/skW3AZ6uGd) [](https://join.slack.com/t/restatecommunity/shared_invite/zt-2v9gl005c-WBpr167o5XJZI1l7HWKImA)
Follow us on Twitter, LinkedIn, Bluesky.
[](https://twitter.com/restatedev) [](https://www.linkedin.com/company/restatedev) [](https://bsky.app/profile/restate.dev)