Architecture decision records
Every non-obvious choice in the template has a record: the context, the decision, the consequences, and - for patterns we left out - the condition under which you should add them.
| # | Decision | One line |
|---|---|---|
| 001 | Feature-first layout, rings inside | Delete a feature = delete a folder; rings still enforced |
| 002 | No DI library | FastAPI Depends + explicit overrides in bootstrap |
| 003 | No mediator, no CQRS bus | Use cases are classes you call; read models when queries hurt |
| 004 | Transaction per request, no Unit of Work | Session opened/committed by the HTTP layer |
| 005 | Domain events dispatched in-process | Outbox + broker only when a handler can't be awaited |
| 006 | FastAPI, SQLAlchemy 2 async, Alembic, uv, Ruff, mypy | Mainstream, typed, replaceable at the edges |
| 007 | Shape in Pydantic, rules in the domain | The domain must hold on every entry path |
| 008 | Separate row model + explicit mapping | Frozen dataclass domain, table free to evolve |
| 009 | Authentication is an adapter, authorization a rule | API key → Actor; use cases decide, domain records organizer_id |
| 010 | Optimistic concurrency on aggregates | version checked on save; stale write → ConflictError → 409, on both adapters |
Format: Michael Nygard's ADR. Add a new one with the next number when you change a decision in your fork; keep the old one and mark it superseded.