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:
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
Layered Architecture
The inward dependency rule, why it matters for testability, and the linters that enforce it.
Coordinator-Worker Model
Serial state ownership, parallel execution, and the durability boundary between them.
Data Flow
Where run state, attempts, counters, caches, and events physically live — and how they move.
Storage Model
sqlc-generated queries, the CQRS Manager, narrow interface projections, and safe migrations.
Extension Points
How strategies, connectors, pipeline stages, and modules plug in through fx option groups.