Architecture

Architecture

How Ductor is built — a layered, fx-composed Go engine with a single-writer coordinator, Postgres for durable state, and Redis for the coordinating layer.

The clearing lifecycle — priced, routed, executed, settled, proven — rests on a set of runtime guarantees: that state survives a crash, that work is never silently dropped or double-sent, and that every decision is recorded. This section is how those guarantees are built. Ductor is a Go engine organized so that its correctness properties fall out of its structure rather than being bolted on. Three ideas carry most of the weight: a strict layering rule, a single-writer coordinator, and a deliberate split of state between Postgres and Redis. This section is the architectural tour; each page below goes deep on one of them.

The shape of the system

Dependencies point inward. The engine is layered — domain at the center, application orchestrating over interfaces the domain declares, and infrastructure and transport on the outside implementing and adapting those contracts. Nothing in the business logic imports Postgres, Redis, HTTP, or protobuf. Shared zero-infra libraries live in pkg/, project-private utilities in internal/, the composition root in cmd/ductor/, and optional feature modules in modules/. Automated dependency checks enforce the direction of those boundaries.

Every arrow below points toward the center, and a custom linter fails the build if one ever points outward — domain cannot import application, and neither can reach infrastructure or transport:

cmd: composition root transport application infrastructure domain

One writer owns the commit surface. Deciding what a run does next is a serial state transition, so a single Coordinator owns it and is the sole writer of run state. Doing the work is parallel, so many Step Workers execute append-only across the fleet. The ordering rule — compute a pure result, commit to Postgres under a version check, then dispatch side effects — is what buys exactly-once state progress and at-least-once side effects.

State lives where its job demands. Postgres is the durable source of truth for anything that must survive a crash; Redis (or Dragonfly) is the fast, cross-pod coordinating layer for queues, counters, and caches. Redis is an accelerator, not the system of record.

Extension happens at the seams. New strategies, connectors, pipeline stages, and lifecycle hooks register through go.uber.org/fx option groups — no engine fork, no edit to the composition root.

Explore the architecture