master/docs/CODESTYLE.md
2026-03-26 21:56:10 +03:00

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 only
  • usecase/ contains application logic and ports
  • repository/ contains repository implementations
  • adapter/ contains framework, config, DI, HTTP, and observability integrations
  • Dependencies must point inward
  • domain/ must not import anything from project internals
  • usecase/ may import domain/, but must not import adapter/, repository/, FastAPI, or OpenTelemetry
  • repository/ and adapter/ may depend on usecase/ ports and domain/

Dependency inversion

  • Define interfaces as Protocol types 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__.py files 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, and Tracer ports
  • OpenTelemetry code stays in adapter/otel/
  • Use Noop implementations 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/ or usecase/

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.