[feat] setup master from fork with fastapi + otel
This commit is contained in:
parent
597eae9b97
commit
1fbf77f879
8 changed files with 310 additions and 1 deletions
85
AGENTS.md
Normal file
85
AGENTS.md
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# Project Guide
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- Python clean architecture template
|
||||||
|
- FastAPI is the current HTTP adapter
|
||||||
|
- The web layer must stay replaceable
|
||||||
|
- Repository and usecase instances are created once at startup
|
||||||
|
- Config comes from YAML plus env into one dataclass tree
|
||||||
|
- Logs metrics and traces stay behind interfaces and use OTel adapters
|
||||||
|
- Request logging middleware is used
|
||||||
|
- Metrics middleware is used
|
||||||
|
- Custom trace middleware is not used
|
||||||
|
- API versioning lives under `/api/v1`
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
- `domain/` core model and domain errors
|
||||||
|
- `domain/error.py` domain errors
|
||||||
|
- `domain/user.py` example domain model
|
||||||
|
- `usecase/` interfaces and usecases
|
||||||
|
- `usecase/interface.py` repository and observability interfaces
|
||||||
|
- `usecase/user.py` example user usecase
|
||||||
|
- `repository/` repository implementations
|
||||||
|
- `repository/user.py` example in-memory repository
|
||||||
|
- `adapter/config/` config models and loader
|
||||||
|
- `adapter/otel/` logger metrics tracer and OTel setup
|
||||||
|
- `adapter/di/` container and startup wiring
|
||||||
|
- `adapter/http/fastapi/` app dependencies lifespan middleware routers
|
||||||
|
- `config/` app YAML and OTel collector config
|
||||||
|
- `docs/` ADR files
|
||||||
|
- `tasks.md` task list
|
||||||
|
- `main.py` local entrypoint
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
- Keep dependency direction inward
|
||||||
|
- `domain` imports nothing internal
|
||||||
|
- `usecase` may import `domain`
|
||||||
|
- `repository` may import `usecase` and `domain`
|
||||||
|
- `adapter` may import `usecase` and `domain`
|
||||||
|
- Do not import FastAPI into `domain` or `usecase`
|
||||||
|
- Do not import OpenTelemetry into `domain` or `usecase`
|
||||||
|
- Keep HTTP models and middleware inside `adapter/http/fastapi/`
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
- Use `tasks.md` for planning
|
||||||
|
- Do not use Beads
|
||||||
|
- Do not use `bd`
|
||||||
|
- Use `uv` for Python commands and dependency management
|
||||||
|
- Do not create commits on your own
|
||||||
|
- Work on one task at a time
|
||||||
|
- Prefer delegation for implementation
|
||||||
|
- Delegate only one task at a time
|
||||||
|
- After one task return to the user with result verification and next options
|
||||||
|
- Wait for the user before the next task commit or fix
|
||||||
|
|
||||||
|
## Makefile
|
||||||
|
- `make install` install deps with `uv`
|
||||||
|
- `make run` start the app locally
|
||||||
|
- `make run-otel` start the app with OTel env
|
||||||
|
- `make lint` run `ruff`
|
||||||
|
- `make typecheck` run `mypy`
|
||||||
|
- `make test` run `pytest`
|
||||||
|
- `make pre-commit` run lint typecheck test
|
||||||
|
- `make compose-build` build the containers
|
||||||
|
- `make compose-up` start app and collector
|
||||||
|
- `make compose-down` stop the stack
|
||||||
|
- `make compose-logs` show app and collector logs
|
||||||
|
- `make compose-ps` show compose status
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
- Local run needs `APP_API_TOKEN` and `APP_SIGNING_KEY`
|
||||||
|
- Base config lives in `config/app.yaml`
|
||||||
|
- Env overrides come from shell vars or `.env`
|
||||||
|
- OTel collector config lives in `config/otel-collector.yaml`
|
||||||
|
|
||||||
|
## Style
|
||||||
|
- Comments are short and have no trailing period
|
||||||
|
- Error messages are short and have no trailing period
|
||||||
|
- Use single-word comments when possible
|
||||||
|
- Use single-word error messages when possible
|
||||||
|
- Keep names simple
|
||||||
|
- Keep adapters thin
|
||||||
|
- Keep `__init__.py` empty
|
||||||
|
- Prefer explicit wiring over magic
|
||||||
|
- Do not expand scope without user approval
|
||||||
|
- Do not `from __future__ import annotations`
|
||||||
16
docs/001-composition-root-and-lifetimes.md
Normal file
16
docs/001-composition-root-and-lifetimes.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 001 Composition Root and Lifetimes
|
||||||
|
|
||||||
|
Context
|
||||||
|
- The template must initialize repository and usecase objects once and reuse them across requests.
|
||||||
|
- FastAPI integration must not leak framework concerns into inner layers.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
- Keep a single composition root in `adapter/di/container.py`.
|
||||||
|
- Build repositories, usecases, config, and observability adapters during application startup.
|
||||||
|
- Store the container in application state and expose instances through thin HTTP dependencies.
|
||||||
|
- Do not create repository or usecase objects per request.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
- Object lifetimes stay explicit and testable.
|
||||||
|
- Replacing the web framework affects only the HTTP adapter layer.
|
||||||
|
- Startup failures surface early instead of during request handling.
|
||||||
16
docs/002-config-yaml-plus-env.md
Normal file
16
docs/002-config-yaml-plus-env.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 002 Config From YAML and Env
|
||||||
|
|
||||||
|
Context
|
||||||
|
- The service needs readable project configuration and safe handling of sensitive values.
|
||||||
|
- The final configuration object should be easy to inject and test.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
- Keep non-sensitive defaults in YAML files under `config/`.
|
||||||
|
- Read sensitive values only from environment variables.
|
||||||
|
- Merge YAML and env sources into one `AppConfig` dataclass tree in `adapter/config/`.
|
||||||
|
- Keep configuration parsing and validation outside `domain/` and `usecase/`.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
- Local configuration remains simple to inspect and version.
|
||||||
|
- Secrets stay out of committed files.
|
||||||
|
- Inner layers depend on typed config data, not on env or YAML readers.
|
||||||
17
docs/003-observability-via-interfaces.md
Normal file
17
docs/003-observability-via-interfaces.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 003 Observability Via Interfaces
|
||||||
|
|
||||||
|
Context
|
||||||
|
- Logging, metrics, and tracing are required, but inner layers must stay independent from OpenTelemetry.
|
||||||
|
- The project needs request logging and metrics middleware.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
- Define observability interfaces in `usecase/interface.py`.
|
||||||
|
- Implement those interfaces with OpenTelemetry adapters in `adapter/otel/`.
|
||||||
|
- Use request logging middleware and metrics middleware in the FastAPI adapter.
|
||||||
|
- Do not add a custom trace middleware by default.
|
||||||
|
- Use standard ASGI/FastAPI OpenTelemetry instrumentation for request spans.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
- Usecases stay portable and framework-agnostic.
|
||||||
|
- Trace behavior remains consistent with OpenTelemetry defaults.
|
||||||
|
- Extra span enrichment can be added later without changing inner-layer contracts.
|
||||||
16
docs/004-versioned-http-api.md
Normal file
16
docs/004-versioned-http-api.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# 004 Versioned HTTP API
|
||||||
|
|
||||||
|
Context
|
||||||
|
- The template needs explicit API versioning and an HTTP boundary that can be replaced later.
|
||||||
|
- FastAPI is the initial framework, but it must not become a hard dependency for core logic.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
- Place HTTP transport code under `adapter/http/fastapi/`.
|
||||||
|
- Mount routers under `/api/v1` and keep version-specific routers in `routers/v1/`.
|
||||||
|
- Map HTTP requests to usecase calls through thin dependencies and handlers only.
|
||||||
|
- Keep framework-specific request and response models in the HTTP adapter layer.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
- New API versions can be introduced without changing usecase contracts.
|
||||||
|
- Replacing FastAPI means rewriting only the HTTP adapter.
|
||||||
|
- Domain and usecase layers remain free from transport concerns.
|
||||||
17
docs/005-fastapi-otel-early-instrumentation.md
Normal file
17
docs/005-fastapi-otel-early-instrumentation.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# 005 Early FastAPI OTel Instrumentation
|
||||||
|
|
||||||
|
Context
|
||||||
|
- HTTP spans and HTTP metrics are provided by FastAPI/ASGI OpenTelemetry middleware.
|
||||||
|
- Starlette caches `middleware_stack` on first ASGI entry, including lifespan startup.
|
||||||
|
- Instrumenting FastAPI inside lifespan is too late for the initial middleware stack.
|
||||||
|
|
||||||
|
Decision
|
||||||
|
- Build the application container before returning the FastAPI app from `create_app`.
|
||||||
|
- Configure FastAPI OpenTelemetry instrumentation in the app factory, not in lifespan.
|
||||||
|
- Pass the configured tracer and meter providers directly to `FastAPIInstrumentor.instrument_app(...)`.
|
||||||
|
- Keep lifespan focused on shutdown and resource cleanup.
|
||||||
|
|
||||||
|
Consequences
|
||||||
|
- `OpenTelemetryMiddleware` is present in the runtime stack without manual stack rebuilds.
|
||||||
|
- HTTP traces and HTTP metrics use the same startup wiring as other singleton adapters.
|
||||||
|
- Observability bootstrap stays explicit in the outer adapter layer.
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[project]
|
[project]
|
||||||
name = "web-python-skelet"
|
name = "master"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
description = ""
|
description = ""
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
|
||||||
142
tasks.md
Normal file
142
tasks.md
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
# План работ: web-python-skelet
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
- Источник требований: `AGENTS.md` и ADR `docs/001`-`docs/004`
|
||||||
|
- Текущее состояние: в `adapter/`, `domain/`, `usecase/`, `repository/`, `test/` пока только `__init__.py`
|
||||||
|
- Отсутствуют рабочие каталоги и файлы из целевой структуры: `adapter/config/`, `adapter/otel/`, `adapter/di/`, `adapter/http/fastapi/`, `config/`, `main.py`
|
||||||
|
- Ограничения: `docs/` и `tasks.md` не добавлять в git; коммиты не делать; работать по одной задаче
|
||||||
|
- ADR пока покрывают архитектуру, новые ADR нужны только если по ходу работ изменится решение
|
||||||
|
|
||||||
|
## Правила выполнения
|
||||||
|
|
||||||
|
- Каждую задачу выполнять отдельным заходом, без параллельной реализации
|
||||||
|
- Каждый субагент отдает diff, список измененных файлов и проверку, но не делает commit
|
||||||
|
- Если в задаче всплывает архитектурное изменение, остановиться и вынести вопрос на согласование
|
||||||
|
|
||||||
|
## Очередь задач
|
||||||
|
|
||||||
|
### T01. Базовый каркас домена и usecase
|
||||||
|
|
||||||
|
- Исполнитель: `primary-agent` (scaffolding)
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: нет
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: создать базовые файлы и контракты в `domain/`, `usecase/`, `repository/`
|
||||||
|
- Файлы: `domain/error.py`, `domain/user.py`, `usecase/interface.py`, `usecase/user.py`, `repository/user.py`
|
||||||
|
- Критерии приемки: зависимости направлены внутрь; в `domain/` и `usecase/` нет FastAPI/OTel; есть пример сущности, ошибок, портов и простого usecase
|
||||||
|
|
||||||
|
### T02. Конфиг из YAML и env
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T01`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: собрать typed-config слой в `adapter/config/` и подготовить базовые yaml-файлы
|
||||||
|
- Файлы: `adapter/config/*`, `config/app.yaml`
|
||||||
|
- Критерии приемки: конфиг собирается в одну dataclass-структуру; секреты читаются из env; парсинг и валидация не протекают в inner layers
|
||||||
|
|
||||||
|
### T03. Observability порты и OTel adapter
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T01`, `T02`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: реализовать логгер, метрики, трейсинг и bootstrap OTel в `adapter/otel/` через интерфейсы из `usecase/interface.py`
|
||||||
|
- Файлы: `adapter/otel/*`, `config/otel-collector.yaml`
|
||||||
|
- Критерии приемки: inner layers знают только интерфейсы; OTLP exporter настраивается из конфига; нет кастомного trace middleware
|
||||||
|
|
||||||
|
### T04. Composition root и lifetime singleton-объектов
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T01`, `T02`, `T03`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: собрать контейнер и startup wiring в `adapter/di/`
|
||||||
|
- Файлы: `adapter/di/container.py`, `adapter/di/__init__.py`
|
||||||
|
- Критерии приемки: repository/usecase создаются один раз на старте; контейнер хранит инстансы явно; нет пересоздания на HTTP-запрос
|
||||||
|
|
||||||
|
### T05. FastAPI adapter как заменяемый web layer
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T04`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: поднять HTTP adapter в `adapter/http/fastapi/` с app factory, lifespan, dependencies, middleware и router ` /api/v1`
|
||||||
|
- Файлы: `adapter/http/fastapi/*`, `main.py`
|
||||||
|
- Критерии приемки: FastAPI изолирован в adapter-слое; handlers тонкие; request logging и metrics middleware подключены; usecase/repository берутся из контейнера
|
||||||
|
|
||||||
|
### T06. Локальный runtime и compose-окружение
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T02`, `T03`, `T05`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: добавить контейнерный runtime для сервиса и compose-окружение с OTel и UI для просмотра логов, метрик и трейсов
|
||||||
|
- Файлы: `Dockerfile`, `docker-compose.yml`
|
||||||
|
- Ограничения: не трогать репозиторный `config/app.yaml`; docker должен прокидывать свой runtime-config внутрь контейнера; Dockerfile только с двумя стадиями `build` и `run`
|
||||||
|
- Критерии приемки: `make compose-build` и `make compose-up` опираются на существующие файлы; сервис поднимается в контейнере; OTel telemetry уходит в dockerized stack; есть UI для просмотра логов, метрик и трейсов; для локального docker-окружения достаточно только `Dockerfile` и `docker-compose.yml`
|
||||||
|
|
||||||
|
### T07. Тесты на lifetimes, config и HTTP smoke
|
||||||
|
|
||||||
|
- Субагент: `test-engineer`
|
||||||
|
- Статус: pending
|
||||||
|
- Зависимости: `T01`, `T02`, `T03`, `T04`, `T05`, `T06`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: покрыть тестами ключевые архитектурные гарантии
|
||||||
|
- Файлы: `test/*`
|
||||||
|
- Критерии приемки: есть тест на singleton lifetime для repository/usecase; есть тест merge YAML+env; есть smoke-тест для ` /api/v1`; тесты не тянут FastAPI/OTel в inner layers
|
||||||
|
|
||||||
|
### T08. Архитектурный и boundary review
|
||||||
|
|
||||||
|
- Субагент: `code-reviewer`
|
||||||
|
- Статус: pending
|
||||||
|
- Зависимости: `T07`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: проверить импорты, соблюдение слоев, startup lifetimes и заменяемость web adapter
|
||||||
|
- Файлы: весь измененный код
|
||||||
|
- Критерии приемки: dependency direction не нарушен; FastAPI и OTel не протекают в `domain/` и `usecase/`; замечания сформулированы как точечные правки или подтверждение готовности к review
|
||||||
|
|
||||||
|
### T09. Конфигурируемый runtime observability
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T03`, `T04`, `T05`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: сделать конфигурируемый runtime observability с настраиваемым sink и форматом логов, плюс отдельными флагами для метрик и трейсов
|
||||||
|
- Файлы: `adapter/config/*`, `adapter/observability/*`, `adapter/otel/*`, `adapter/di/*`, `adapter/http/fastapi/*`, `config/app.yaml`, при необходимости `main.py`
|
||||||
|
- Конфиг: `logging.output=stdout|file|otel`, `logging.format=text|json`, `logging.file_path=...`, `metrics.enabled=true|false`, `tracing.enabled=true|false`
|
||||||
|
- Решение: вынести выбор runtime в отдельный adapter-layer factory; `domain/` и `usecase/` не менять; для `stdout` и `file` поддержать оба формата `text` и `json`; `logging.file_path` использовать только при `logging.output=file`; при отключенных метриках и трейсах использовать `Noop`-реализации; OTel runtime поднимать только если нужен хотя бы для одного сигнала
|
||||||
|
- Критерии приемки: при `logging.output=stdout` логи идут в stdout в формате `text` или `json` по конфигу; при `logging.output=file` логи пишутся в файл по пути `logging.file_path` в формате `text` или `json`; при `logging.output=otel` логи уходят в collector; `metrics.enabled=false` отключает метрики и metrics middleware; `tracing.enabled=false` отключает FastAPI instrumentation и tracer runtime; DI продолжает отдавать единый runtime через контейнер; внутренние слои по-прежнему знают только порты
|
||||||
|
|
||||||
|
### T10. ADR: раннее подключение OTel к FastAPI
|
||||||
|
|
||||||
|
- Исполнитель: `primary-agent` (docs)
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: нет
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: зафиксировать правило, что FastAPI OTel instrumentation выполняется до первой сборки `middleware_stack`
|
||||||
|
- Файлы: `docs/005-fastapi-otel-early-instrumentation.md`
|
||||||
|
- Критерии приемки: ADR занимает 10-20 строк; описаны context, decision, consequences; решение не переписывает историю прошлых ADR
|
||||||
|
|
||||||
|
### T11. Перенос FastAPI OTel bootstrap в app factory
|
||||||
|
|
||||||
|
- Субагент: `feature-developer`
|
||||||
|
- Статус: completed
|
||||||
|
- Зависимости: `T10`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: перенести создание container, установку `FastAPIInstrumentor.instrument_app(...)` из `lifespan` в `create_app`, оставив в `lifespan` только shutdown
|
||||||
|
- Файлы: `adapter/http/fastapi/app.py`, `adapter/http/fastapi/lifespan.py`, при необходимости `adapter/http/fastapi/dependencies.py`
|
||||||
|
- Ограничения: не использовать ручной rebuild `app.middleware_stack`; не менять `domain/` и `usecase/`; не добавлять бизнес-логику; сохранить singleton-lifetime container
|
||||||
|
- Критерии приемки: instrumentation происходит до первой сборки middleware stack; `OpenTelemetryMiddleware` попадает в runtime stack без workaround; shutdown закрывает instrumentation и runtime один раз; compose-конфиг продолжает работать
|
||||||
|
|
||||||
|
### T12. Регрессионная проверка HTTP telemetry wiring
|
||||||
|
|
||||||
|
- Субагент: `test-engineer`
|
||||||
|
- Статус: pending
|
||||||
|
- Зависимости: `T11`
|
||||||
|
- Commit required: no
|
||||||
|
- Scope: добавить проверку, что раннее instrumentation wiring сохраняется и не требует ручного rebuild middleware stack
|
||||||
|
- Файлы: `test/*`
|
||||||
|
- Ограничения: без реального collector; проверять через ASGI/lifespan или локальные assertions по app runtime; не тянуть FastAPI и OTel в inner-layer тесты
|
||||||
|
- Критерии приемки: тест подтверждает, что при включенных metrics/tracing `OpenTelemetryMiddleware` присутствует в runtime stack; тест не зависит от внешнего OTel collector; существующие архитектурные границы не нарушены
|
||||||
Loading…
Add table
Add a link
Reference in a new issue