# Architecture (/docs/architecture)



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 [#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`:

```mermaid
flowchart LR
  cmd[cmd: composition root] --> transport
  transport --> application
  infrastructure --> application
  application --> domain
  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](https://uber-go.github.io/fx/)
option groups — no engine fork, no edit to the composition root.

## Explore the architecture [#explore-the-architecture]

<Cards>
  <Card title="Layered Architecture" href="/docs/architecture/layered-architecture">
    The inward dependency rule, why it matters for testability, and the linters that enforce it.
  </Card>

  <Card title="Coordinator-Worker Model" href="/docs/architecture/coordinator-worker-model">
    Serial state ownership, parallel execution, and the durability boundary between them.
  </Card>

  <Card title="Data Flow" href="/docs/architecture/data-flow">
    Where run state, attempts, counters, caches, and events physically live — and how they move.
  </Card>

  <Card title="Storage Model" href="/docs/architecture/storage-model">
    sqlc-generated queries, the CQRS Manager, narrow interface projections, and safe migrations.
  </Card>

  <Card title="Extension Points" href="/docs/architecture/extension-points">
    How strategies, connectors, pipeline stages, and modules plug in through fx option groups.
  </Card>
</Cards>
