FastAPI Clean Architecture — Review Skill
Audits the project in the current working directory for Clean Architecture compliance, reading one layer at a time. Every violation is reported with its file, line, the architectural rule broken, and how to fix it. Pass --fix to apply fixes automatically after reporting.
For scaffolding a new project use /fastapi-clean-architecture-template. To activate rules for the current session use /fastapi-clean-architecture-mode.
Workflow
Read and audit one layer at a time. Report findings as you go, then produce a final summary. This keeps each read batch small.
Phase 1 — Domain layer (src/domain/)
Read all files in src/domain/.
Check:
- Import direction: no imports from
src/application/, src/infrastructure/, or src/api/
- Naming: entity classes are singular nouns; repository interfaces end with
Base; enums use StrEnum with lowercase values and live in src/domain/enums/; no entity-specific result enums (e.g. CreateUserResult is a violation)
- Documentation: every
.py file has a module docstring; every class has a class docstring; every __init__ has a docstring; public ABC methods have Google-style docstrings
Phase 2 — Application layer (src/application/)
Read all files in src/application/.
Check:
- Import direction: no imports from
src/infrastructure/ or src/api/
- Naming: use case ABCs end with
Base; DTOs are frozen dataclasses with DTO suffix; no wrapper collection DTOs (e.g. UserListDTO is a violation); return types use list[UserDTO] directly
- DI: injectable
__init__ methods have @inject
- Repository pattern: use cases contain no exception handling for mutations — they forward repository results as-is; no direct session or DB access
- Documentation: same rules as Phase 1
Phase 3 — Infrastructure layer (src/infrastructure/)
Read all files in src/infrastructure/.
Check:
- Import direction: no imports from
src/api/
- Repository pattern:
- Each method performs exactly one CRUD operation — flag any method that combines read + write
- Mutation methods catch all exceptions internally and return result enums; nothing propagates
- Exception mapping:
IntegrityError → UNIQUE_CONSTRAINT_ERROR, DeadlockDetectedError → CONCURRENCY_ERROR, all others → FAILURE
- No session parameters in repository constructors or method signatures — repos inject
ConnectionFactoryBase
- Every method uses
async with self._connection_factory.get_session()
- DB constraints (SQLAlchemy only):
- Every
UniqueConstraint, ForeignKeyConstraint, CheckConstraint, Index has an explicit name
- Naming pattern:
uq_, fk_, ck_, ix_
- Constraints declared in
__table_args__, not as column-level shorthand (except primary key)
id, created_at, updated_at never set in Python code
session.refresh() called after every insert and update
SQLAlchemyEnum type defined at module level, not inline
- DI:
@inject on every injectable __init__; singleton scope only on ConnectionFactory and external service clients — never on repos
- Documentation: same rules as Phase 1
Phase 4 — API layer (src/api/)
Read all files in src/api/.
Check:
- Import direction: no imports from
src/infrastructure/
- Naming: schemas end with
Request or Response and inherit APIModelBase; no bare schema classes
- DI:
- Routes use
Injected(BaseClass) for use cases and services — flag any Depends() used for this purpose
Depends() only permitted in src/api/dependencies/ and in dependencies=[...] on APIRouter
- Guard functions defined inside route files (should be in
src/api/dependencies/)
Depends(get_current_user) in individual route signatures instead of on APIRouter
- Code style: lines over 80 chars (excluding
# noqa: E501); List[X], Optional[X], Dict[K,V] instead of modern annotations; sync DB calls
- Documentation: same rules as Phase 1
Phase 5 — Container and entry point (src/container.py, src/main.py)
Read both files.
Check:
main.py middleware order: InjectorMiddleware added before attach_injector(); attach_injector() called before include_router()
container.py: every Base/implementation pair that exists in the codebase has a binder.bind() entry; no manual instantiation of injectable classes
- Singleton scope: only
ConnectionFactory and external service clients — flag repos or use cases bound as singleton
Phase 6 — Global checks
- Boolean naming: scan all files for boolean fields/variables not prefixed with
is_, has_, or can_
- Abbreviations: flag
repo, conn, svc, mgr, cfg anywhere in identifiers
- Single-letter or vague names:
g, dto, result, data as variable names
Report format
## Architecture Review — <project-name>
### Violations: <N>
| # | File | Line | Rule | Fix |
|---|------|------|------|-----|
| 1 | src/domain/entities/user.py | 3 | Domain imports from Infrastructure | Remove import of `UserRepository` |
...
### Layer results
- Domain: ✅ / ⚠️ <N violations>
- Application: ✅ / ⚠️ <N violations>
- Infrastructure: ✅ / ⚠️ <N violations>
- API: ✅ / ⚠️ <N violations>
- Container/Main: ✅ / ⚠️ <N violations>
- Global: ✅ / ⚠️ <N violations>
If --fix was passed, list every change made after the table.
Source: joe-vi/Templates — distributed by TomeVault.
1---2name: fastapi-clean-architecture-review3description: Audit an existing FastAPI project for Clean Architecture compliance — verifies unidirectional layer dependencies, correct abstraction boundaries, repository pattern correctness, DI wiring, naming conventions, DB constraint rules, and documentation standards. Reports every violation with file and line number. Use when this capability is needed.4---56# FastAPI Clean Architecture — Review Skill78Audits the project in the current working directory for Clean Architecture compliance, reading one layer at a time. Every violation is reported with its file, line, the architectural rule broken, and how to fix it. Pass `--fix` to apply fixes automatically after reporting.910For scaffolding a new project use `/fastapi-clean-architecture-template`. To activate rules for the current session use `/fastapi-clean-architecture-mode`.1112---1314## Workflow1516Read and audit one layer at a time. Report findings as you go, then produce a final summary. This keeps each read batch small.1718### Phase 1 — Domain layer (`src/domain/`)1920Read all files in `src/domain/`.2122Check:23- **Import direction**: no imports from `src/application/`, `src/infrastructure/`, or `src/api/`24- **Naming**: entity classes are singular nouns; repository interfaces end with `Base`; enums use `StrEnum` with lowercase values and live in `src/domain/enums/`; no entity-specific result enums (e.g. `CreateUserResult` is a violation)25- **Documentation**: every `.py` file has a module docstring; every class has a class docstring; every `__init__` has a docstring; public ABC methods have Google-style docstrings2627### Phase 2 — Application layer (`src/application/`)2829Read all files in `src/application/`.3031Check:32- **Import direction**: no imports from `src/infrastructure/` or `src/api/`33- **Naming**: use case ABCs end with `Base`; DTOs are frozen dataclasses with `DTO` suffix; no wrapper collection DTOs (e.g. `UserListDTO` is a violation); return types use `list[UserDTO]` directly34- **DI**: injectable `__init__` methods have `@inject`35- **Repository pattern**: use cases contain no exception handling for mutations — they forward repository results as-is; no direct session or DB access36- **Documentation**: same rules as Phase 13738### Phase 3 — Infrastructure layer (`src/infrastructure/`)3940Read all files in `src/infrastructure/`.4142Check:43- **Import direction**: no imports from `src/api/`44- **Repository pattern**:45 - Each method performs exactly one CRUD operation — flag any method that combines read + write46 - Mutation methods catch all exceptions internally and return result enums; nothing propagates47 - Exception mapping: `IntegrityError` → `UNIQUE_CONSTRAINT_ERROR`, `DeadlockDetectedError` → `CONCURRENCY_ERROR`, all others → `FAILURE`48 - No session parameters in repository constructors or method signatures — repos inject `ConnectionFactoryBase`49 - Every method uses `async with self._connection_factory.get_session()`50- **DB constraints** (SQLAlchemy only):51 - Every `UniqueConstraint`, `ForeignKeyConstraint`, `CheckConstraint`, `Index` has an explicit `name`52 - Naming pattern: `uq_`, `fk_`, `ck_`, `ix_`53 - Constraints declared in `__table_args__`, not as column-level shorthand (except primary key)54 - `id`, `created_at`, `updated_at` never set in Python code55 - `session.refresh()` called after every insert and update56 - `SQLAlchemyEnum` type defined at module level, not inline57- **DI**: `@inject` on every injectable `__init__`; `singleton` scope only on `ConnectionFactory` and external service clients — never on repos58- **Documentation**: same rules as Phase 15960### Phase 4 — API layer (`src/api/`)6162Read all files in `src/api/`.6364Check:65- **Import direction**: no imports from `src/infrastructure/`66- **Naming**: schemas end with `Request` or `Response` and inherit `APIModelBase`; no bare schema classes67- **DI**:68 - Routes use `Injected(BaseClass)` for use cases and services — flag any `Depends()` used for this purpose69 - `Depends()` only permitted in `src/api/dependencies/` and in `dependencies=[...]` on `APIRouter`70 - Guard functions defined inside route files (should be in `src/api/dependencies/`)71 - `Depends(get_current_user)` in individual route signatures instead of on `APIRouter`72- **Code style**: lines over 80 chars (excluding `# noqa: E501`); `List[X]`, `Optional[X]`, `Dict[K,V]` instead of modern annotations; sync DB calls73- **Documentation**: same rules as Phase 17475### Phase 5 — Container and entry point (`src/container.py`, `src/main.py`)7677Read both files.7879Check:80- **`main.py` middleware order**: `InjectorMiddleware` added before `attach_injector()`; `attach_injector()` called before `include_router()`81- **`container.py`**: every Base/implementation pair that exists in the codebase has a `binder.bind()` entry; no manual instantiation of injectable classes82- **Singleton scope**: only `ConnectionFactory` and external service clients — flag repos or use cases bound as singleton8384### Phase 6 — Global checks8586- **Boolean naming**: scan all files for boolean fields/variables not prefixed with `is_`, `has_`, or `can_`87- **Abbreviations**: flag `repo`, `conn`, `svc`, `mgr`, `cfg` anywhere in identifiers88- **Single-letter or vague names**: `g`, `dto`, `result`, `data` as variable names8990---9192## Report format9394```95## Architecture Review — <project-name>9697### Violations: <N>9899| # | File | Line | Rule | Fix |100|---|------|------|------|-----|101| 1 | src/domain/entities/user.py | 3 | Domain imports from Infrastructure | Remove import of `UserRepository` |102...103104### Layer results105- Domain: ✅ / ⚠️ <N violations>106- Application: ✅ / ⚠️ <N violations>107- Infrastructure: ✅ / ⚠️ <N violations>108- API: ✅ / ⚠️ <N violations>109- Container/Main: ✅ / ⚠️ <N violations>110- Global: ✅ / ⚠️ <N violations>111```112113If `--fix` was passed, list every change made after the table.114115---116> Source: [joe-vi/Templates](https://github.com/joe-vi/Templates) — distributed by [TomeVault](https://tomevault.io).117<!-- tomevault:4.0:skill_md:2026-05-22 -->