Compose, test, and operate

Compose Queues and Workflows

Use a Queue as a high-throughput buffer and create a Workflow only for jobs that need durable multi-step orchestration.

8 minute lesson

~~~

Queues and Workflows are complementary, because they’re good at opposite things. A Queue absorbs bursts and controls consumer concurrency — ten thousand requests in a minute become a steady drip of batches. A Workflow gives one job durable multi-step execution — but creating ten thousand instances directly from HTTP handlers gives you no buffer and no flow control.

So compose them. The endpoint enqueues cheaply and returns fast. The queue smooths the burst. Its consumer can create a Workflow instance for each complex job:

async queue(batch, env) {
  for (const message of batch.messages) {
    const { jobId, orderId } = message.body

    try {
      await env.ORDER_WORKFLOW.create({ id: jobId, params: { orderId } })
    } catch (e) {
      // instance with this ID already exists: duplicate delivery, safe to ignore
    }

    message.ack()
  }
}

The try/catch is not decoration. Queue delivery is at-least-once, so this consumer will occasionally see the same message twice. Make workflow creation idempotent because the Queue message can repeat — and here the deterministic instance ID does the work. Creating an instance with an ID that already exists throws instead of spawning a second workflow, so the duplicate collapses into a no-op. If you let the platform pick random instance IDs in this consumer, every duplicate delivery becomes a duplicate order pipeline.

One ID through the whole system

Use one stable job ID across HTTP, queue, workflow, D1, and logs. The endpoint generates jobId, the queue message carries it, the Workflow instance is named by it, and the database rows reference it:

npx wrangler workflows instances describe order-workflow job_01J2AB3CD4
# status: running
# step "charge payment": complete

When a customer asks about their order, one identifier traces the path through every layer. Break the chain — random workflow IDs, unrelated database keys — and every support question becomes an archaeology project.

Don’t orchestrate the trivial

Composition has a cost: more moving parts, more to observe. Simple single-step work should remain a Queue job instead of gaining unnecessary orchestration. A welcome email doesn’t need a Workflow; the queue’s own retries already cover a single step. Reserve Workflows for jobs with real sequence — multiple side effects, waits, or state between steps.

Design one endpoint that accepts 100 jobs, buffers them, and starts at most one Workflow per job ID. Then feed it duplicate submissions and prove the “at most one” holds — that’s the property this whole composition exists to guarantee.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →