# Layering Rules ## Summary The dependency direction is fixed. Violating it is a design defect. --- ## Allowed imports ``` interfaces/* -> domain/* (services, ports, models) interfaces/* -> infra/adapters/* (only read-only value objects) domain/* -> domain/* (same layer) domain/* -> pipelines/* (call a pipeline via a port) infra/adapters/* -> domain/ports/* (implement the interface) main.py/init.py -> everything (composition root only) ``` ## Forbidden imports ``` domain/* -> interfaces/* (never) domain/* -> fastapi/starlette (never) infra/adapters/* -> interfaces/* (never) interfaces/* -> infra/db/* (state lives in resources, not imports) ``` ## Why - A pure domain is testable without a server, a database, or an event loop. - Adapters can be swapped (in-memory fake, real database) without touching business logic. - The HTTP edge stays thin and replaceable. ## The port/adapter contract A **port** is an abstract class or protocol declared in `domain//ports/`. An **adapter** implements it in `infra/adapters/`. The composition root binds the two. ``` domain//ports/db.py -> repository port + document model domain//ports/service.py -> service + factory infra/db/repos/.py -> repository adapter factory infra/adapters/service.py -> generic CRUD service base ``` ## Enforcement - Keep every domain module importable with only the standard library and Pydantic. If a domain import pulls in `fastapi`, the layering is broken. - Reviewers (and CI) reject `import fastapi` inside `domain/` or `infra/`.