# Layered Architecture (/docs/architecture/layered-architecture)



Ductor is organized as a set of layers with a **strict inward dependency rule**.
Dependencies point *toward* the domain; nothing in the core business logic knows
about Postgres, Redis, HTTP, or protobuf. This separation keeps the engine
testable and its seams sharp.

## The dependency rule [#the-dependency-rule]

```mermaid
flowchart LR
  T["transport"] --> A["application"]
  A --> D["domain"]
  I["infrastructure"] --> D
  P["pkg (shared, zero-infra)"] --> D
```

* **`domain/`** — pure business entities, domain interfaces, and domain errors.
  Zero infrastructure imports: no `net/http`, no generated proto, no SDKs. Its
  only outside dependency is a small UUID utility. Everything the domain needs
  from the outside world is expressed as an *interface* it declares
  (`domain/interfaces/`), which infrastructure implements.
* **`application/`** — use-case orchestration. It coordinates domain objects
  through injected interfaces and &#x2A;*never imports `infrastructure/`**. This is
  where the routing pipeline and workflow coordinator live.
* **`infrastructure/`** — concrete implementations of the domain's interfaces:
  Postgres stores, Redis queues and caches, connectors, observability. It
  depends on the domain (to implement its contracts) and on external SDKs.
* **`transport/`** — the ingress/egress boundary. HTTP and gRPC handlers that
  translate protocol requests into application calls and back. Depends only on
  `application/`, `domain/`, and generated proto stubs.
* **`pkg/`** — reusable libraries with zero infrastructure imports (strategy
  registry, CEL engine, telemetry helpers).
* **`internal/`** — project-private cross-cutting utilities (config, clock,
  async, security, test helpers).
* **`cmd/ductor/`** — the composition root that wires every layer together.
* **`modules/`** — optional feature modules that self-register.

## Why the rule matters [#why-the-rule-matters]

The point of pointing dependencies inward is **testability and substitution**.
Because the application layer depends on interfaces the domain declares — not on
pgx or rueidis — you can unit-test a use case against an in-memory fake, and you
can swap a storage backend without touching business logic. The domain has no
reason to change when you upgrade a database driver.

It also keeps the domain *honest*: a rule can't accidentally reach into Redis or
emit an HTTP response, because those types aren't importable from where it lives.

## Dependency enforcement [#dependency-enforcement]

Automated dependency checks reject imports that cross these layer boundaries.

<Callout title="The composition root">
  All wiring lives in `cmd/ductor/fx_*.go` using
  <a href="https://uber-go.github.io/fx/">go.uber.org/fx</a>. There are no hidden
  global singletons — every dependency is constructed once and injected. If you
  want to know how a thing is built, you read the fx module, not a package-level
  `init()`.
</Callout>

## Narrow interfaces over fat ones [#narrow-interfaces-over-fat-ones]

Even within a layer, Ductor prefers *narrow* contracts. The full storage surface
is a composite (`Storage`, and the CQRS `Manager`), but application services
declare a **minimal local interface** listing only the methods they actually use
— for example, a `RoutingStorageCQRS` interface next to the routing adapter.
This shrinks the mock surface for tests and makes each call site's real
dependencies obvious. New code is encouraged to use the narrow per-aggregate
interfaces rather than the broad composite.

## Services and lifecycle [#services-and-lifecycle]

Long-running subsystems (the workflow coordinator, queue drainers, retention and
archival workers) implement a common `Service` lifecycle: `Pre` (fail-fast setup,
run sequentially), `Run` (the blocking main loop, run concurrently), and `Stop`
(drain in reverse registration order). A `Manager` orchestrates them. This gives
the process a predictable startup and a clean, ordered shutdown.

## Where to go next [#where-to-go-next]

* [The Coordinator-Worker Model, in depth](/docs/architecture/coordinator-worker-model) — the runtime's central pattern.
* [Storage Model](/docs/architecture/storage-model) — how the CQRS Manager and sqlc fit the infrastructure layer.
* [Extension Points](/docs/architecture/extension-points) — how modules contribute without violating the layering.
