Business Logic Guide
Overview
How we split HTTP, services, repositories, and models.
Layering model
- API layer:
app/api/v1/(transport only) - Service layer:
app/services/(business rules) - Repository layer:
app/repositories/(DB access) - Models:
app/models/(ORM entities)
Rule:
- Router does not implement business rules directly.
- Service does not depend on FastAPI request primitives.
- Repository does not return HTTP-specific structures.
Theoretical background
Layered architecture — theoretical background
Layered architecture stacks the system in horizontal layers. Each layer talks only to the layer below. Inner layers (rules, persistence) do not import HTTP or transport details, so you can test and swap adapters.
This is close to ports and adapters: the core defines behavior; HTTP and SQL are adapters.
Contract-first means OpenAPI describes the HTTP surface; implementation must match. The outer adapter stays tied to a stable contract.
Layer responsibilities (reference)
The public API is described first in OpenAPI (“contract-first”): what you see in the spec is
what
callers should rely on. System-wide diagrams (C4) live in
System design; a simplified
request-path
diagram is rendered from docs/uml/architecture/internal_readme_layer_boundaries.puml.
| Layer | Packages | Responsibility | Contract and constraints |
|---|---|---|---|
| HTTP API | app/api/ |
FastAPI routers: declare paths, dependencies, and Pydantic-typed bodies/responses from
app/schemas/; call services. Handlers stay thin (no duplicate field validation logic here).
|
Must match OpenAPI (operations, declared responses, examples). Maps successful outcomes and framework-level HTTP concerns to the right status codes. |
| Schemas & 422 contract | app/schemas/, app/validation/, handlers in app/main.py |
app/schemas/: Pydantic models and field constraints. app/validation/:
translates Pydantic/FastAPI validation failures into the canonical 422 payload shape for
clients. Registration of exception handlers lives next to app setup in app/main.py.
|
Error catalog and examples in OpenAPI; alignment between Pydantic error kinds and documented
code/key values.
|
| Application | app/services/ |
Use cases and business invariants (for example “duplicate composite key”) implemented with domain
logic;
raises HTTPException with business error payloads when rules fail. |
Business errors must match the governed contract. Layering policy: Developers Docs. |
| Persistence | app/repositories/, DB session in app/core/ |
Load and persist entities via SQLAlchemy; Alembic migrations for schema changes. | Storage details stay out of the public HTTP model; DB constraints remain consistent with what the API promises. |
Where validation runs
Where checks happen (in order):
-
Field rules for JSON bodies are defined in Pydantic models under
app/schemas/(required fields, types, max length, and so on). -
When a request arrives, FastAPI runs those checks before code in
app/api/runs. Handlers usually receive data that already passed this step. -
If a check fails,
app/validation/maps the failure to our standard422response (code,key,message). It does not redefine the rules—only the error shape for clients. That wiring is registered inapp/main.py. -
Rules that need the database—such as “this user already exists”—are enforced in
app/services/and surface as business client errors, not as “invalid JSON field” / schema validation. See ADR 0003.
Responsibility boundaries
Router:
- Parse and validate request.
- Call service.
- Shape response model.
- Declare OpenAPI responses.
Service:
- Enforce domain rules.
- Orchestrate repositories.
- Raise domain/business errors.
Repository:
- Isolate query/write mechanics.
- Keep persistence details local.
Transactions and consistency
- Write operations must be atomic.
- Duplicate/conflict checks must be deterministic.
- Any failure path must return stable error contract.
Logging and observability
- Request-level logging: middleware in
app/main.py. - Business event logs: service layer.
- Log format/config:
app/core/logging.py.
Typical change flow
- Define or adjust schema.
- Implement service rule.
- Implement repository operation.
- Wire router.
- Add examples and error mappings.
- Add tests.
- Run make pipeline.
Page history
| Date | Change | Author |
|---|---|---|
| Added Page history section (repository baseline). | Ivan Boyarkin |