Skip to main content
  1. System Design Components/

Archetype 4 — Workflow / Lifecycle State #


What this archetype is #

A workflow row moves through a state machine. Correctness comes from valid transitions and reliable side effects.

Examples: onboarding, approval, order lifecycle.

We will use loan approval workflow as the running example.


Layer 1: Entities and Postgres table design #

WorkflowState
  -> source truth for one workflow instance
create table loan_workflows (
  workflow_id uuid primary key,
  applicant_id bigint not null,
  state text not null,
  payload jsonb not null,
  version bigint not null default 1,
  updated_at timestamptz not null default now()
);

create table workflow_outbox (
  outbox_id bigserial primary key,
  workflow_id uuid not null,
  effect_type text not null,
  effect_payload jsonb not null,
  created_at timestamptz not null default now(),
  delivered_at timestamptz
);

create index workflow_state_updated_idx
on loan_workflows (state, updated_at desc);

Layer 2: Write path mechanics #

Transition with outbox #

begin;

update loan_workflows
set state = 'APPROVED',
    version = version + 1,
    updated_at = now()
where workflow_id = $1
  and version = $2
  and state = 'PENDING_REVIEW';

insert into workflow_outbox (
  workflow_id, effect_type, effect_payload
) values (
  $1, 'SEND_APPROVAL_EMAIL', $3
);

commit;

This is the generic Postgres equivalent of state transition + transactional outbox.

Read workflow #

select *
from loan_workflows
where workflow_id = $1;

Layer 3: Fault tolerance #

  • retry of same transition after commit
  • invalid transition from stale state
  • competing transitions
  • crash after state update but before external side effect -> solved with outbox relay
  • stale status read

Layer 4: Scale #

Default hotspots:

  • hot workflow transition rows
  • expensive status polling
  • outbox relay backlog

Common mitigations:

  • state-indexed queries
  • polling reduction via push notifications
  • separate outbox workers and batching