2.6 KiB
2.6 KiB
Code Style Guide
Purpose
This repository is a Python clean architecture template. Write code that keeps the core portable, adapters thin, and wiring explicit.
Layer rules
domain/contains entities and domain errors onlyusecase/contains application logic and portsrepository/contains repository implementationsadapter/contains framework, config, DI, HTTP, and observability integrations- Dependencies must point inward
domain/must not import anything from project internalsusecase/may importdomain/, but must not importadapter/,repository/, FastAPI, or OpenTelemetryrepository/andadapter/may depend onusecase/ports anddomain/
Dependency inversion
- Define interfaces as
Protocoltypes at the point of use - Keep implementations in outer layers
- Inject dependencies through constructors
- Prefer explicit wiring in
adapter/di/container.py - Do not hide object creation behind magic globals
Python conventions
- Use simple names
- Prefer dataclasses for immutable values, config sections, and request models inside inner layers
- Keep
__init__.pyfiles empty - Do not use
from __future__ import annotations - Prefer one clear responsibility per class
- Keep functions small and direct
Comments and errors
- Add comments only when the code is not obvious
- Keep comments short
- Do not end comments with a period
- Keep error messages short
- Do not end error messages with a period
HTTP rules
- Keep FastAPI code inside
adapter/http/fastapi/ - Keep HTTP request and response schemas inside the HTTP adapter
- Handlers should translate HTTP input to usecase calls and map domain errors to HTTP responses
- Do not move business logic into routers, dependencies, or middleware
Observability rules
- Inner layers know only
Logger,Metrics, andTracerports - OpenTelemetry code stays in
adapter/otel/ - Use
Noopimplementations when a signal is disabled - Request logging belongs in the HTTP adapter
Configuration rules
- Keep non-secret defaults in
config/app.yaml - Read secrets from env vars
- Merge YAML,
.env, and process env into one typed config tree - Do not read env vars directly inside
domain/orusecase/
Example
from typing import Protocol
class UserRepository(Protocol):
def get(self, user_id: str) -> User | None: ...
class GetUser:
def __init__(self, repository: UserRepository, logger: Logger, tracer: Tracer) -> None:
self._repository = repository
self._logger = logger
self._tracer = tracer
The use case owns the contract. The repository implementation lives outside the use case layer.