Part 9 · 1 chapters · ~8 min

Capstone: A Workflow Engine in Miniature

Building a small durable workflow engine on Postgres: workflow and step tables, claiming with SKIP LOCKED and leases, retries with backoff, timers, compensations, idempotent steps, a status API, and tests that kill workers mid-workflow.

12

The build

code
mini-workflows/
  schema.sql        workflows(id, type, state, input, version) · steps(workflow_id, name, state, attempts, result, next_run_at, lease_until)
  engine.ts         define('crossBorder', [step('debit', debit, refund), step('fx', bookFx, reverseFx), step('credit', creditMpesa)])
  runner.ts         loop: claim due steps (FOR UPDATE SKIP LOCKED, lease 60 s) → run → record result or schedule retry with jitter
  compensate.ts     on permanent failure: run compensations of completed steps in reverse, each idempotent
  timers.ts         step.sleep('24h') = next_run_at in the future; no thread held
  api.ts            GET /workflows/:id → state, steps, attempts, last error
tests
  - kill -9 a runner mid-step: another runner reclaims after the lease and the step runs at most once in effect
  - a permanently failing credit step triggers reverse compensations exactly once each
  - 1,000 concurrent workflows complete with 4 runners; measure throughput and the oldest-step age