Skip to main content

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.

#DecisionOne line
001Feature-first layout, rings insideDelete a feature = delete a folder; rings still enforced
002No DI libraryFastAPI Depends + explicit overrides in bootstrap
003No mediator, no CQRS busUse cases are classes you call; read models when queries hurt
004Transaction per request, no Unit of WorkSession opened/committed by the HTTP layer
005Domain events dispatched in-processOutbox + broker only when a handler can't be awaited
006FastAPI, SQLAlchemy 2 async, Alembic, uv, Ruff, mypyMainstream, typed, replaceable at the edges
007Shape in Pydantic, rules in the domainThe domain must hold on every entry path
008Separate row model + explicit mappingFrozen dataclass domain, table free to evolve
009Authentication is an adapter, authorization a ruleAPI key → Actor; use cases decide, domain records organizer_id
010Optimistic concurrency on aggregatesversion 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.