Skip to main content

Idempotency

In this guide, you'll learn how to make operations idempotent.

DBOS allows users to send any request with an idempotency key to guarantee it only executes once, even if the request is sent multiple times. This is especially useful if your operations have side effects like making a payment or sending an email.

Setting Idempotency Keys

DBOS idempotency keys are UUIDs. Idempotency keys are required to be globally unique for your application. There are many popular libraries for generating UUIDs in Typescript, such as uuid.js.

To make a request idempotent, generate a UUID and set the request's dbos-idempotency-key header field to that UUID. No matter how many times you send that request, as long as each request has the idempotency key set, the operation will only execute once (if the request is for a communicator, it may be retried multiple times, but will not re-execute after successfully completing).

info

It's not a coincidence that both idempotency keys and workflow identities are UUIDs. If you run a workflow with an idempotency key UUID, the identity of that execution is set to that UUID.

Manually Setting Idempotency Keys

Idempotency keys are not automatically used for handlers. Instead, if you invoke an operation from a handler, you can manually pass in an idempotency key as an argument to context.invoke. The syntax for invoking Class.operation with an idempotency key is:

  @GetApi(...)
static async exampleHandler(ctxt: HandlerContext, ...) {
const idempotencyKey = ...;
await ctxt.invoke(Class, idempotencyKey).operation(...);
}

Idempotency Example

Let's look at this workflow endpoint (from the example code coming with npx @dbos-inc/create):

  @GetApi('/greeting/:user')
@Workflow()
static async helloWorkflow(ctxt: WorkflowContext, @ArgSource(ArgSources.URL) user: string) {
const greeting = await ctxt.invoke(Hello).helloTransaction(user);
try {
await ctxt.invoke(Hello).greetPostman(greeting);
return greeting;
} catch (e) {
ctxt.logger.error(e);
await ctxt.invoke(Hello).undoHelloTransaction(user);
return `Greeting failed for ${user}\n`
}
}

Each request to this endpoint has the side effect of incrementing a database counter. However, if we set the idempotency key, we can resend a request multiple times without side effects:

If we curl this endpoint normally multiple times, each request increments the counter:

curl http://localhost:3000/greeting/dbos

However, if we set the idempotency key in the header and resend the request many times, each request returns the same response and the workflow only executes once:

curl -H "dbos-idempotency-key: 123e4567-e89b-12d3-a456-426614174000" http://localhost:3000/greeting/dbos