baton/docs/adr/ADR-001-backend-stack.md
2026-03-20 20:44:00 +02:00

97 lines
5.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ADR-001: Выбор бэкенд-стека
**Дата:** 2026-03-20
**Статус:** Accepted
**Автор:** Architect Agent (Kin pipeline, BATON-001)
---
## Контекст
Baton — минималистичное PWA приложение экстренного сигнала. Бэкенд выполняет три задачи:
1. Принять POST /signal от 300-400 пользователей (возможны одновременные запросы)
2. Сохранить сигнал в SQLite
3. Отправить уведомление в Telegram-группу через Bot API
Проект требует простого деплоя на один VPS, без kubernetes, без сложной инфраструктуры.
Команда имеет опыт работы с Python/FastAPI (используется в проекте Kin).
Рассматривались три стека: FastAPI (Python), Express/Fastify (Node.js), Go (net/http).
---
## Варианты
### Вариант A: FastAPI (Python 3.11+)
**Плюсы:**
- Знакомость команды (используется в Kin)
- asyncio нативно
- Pydantic — автоматическая валидация входных данных
- `aiosqlite` или `sqlite3` через `run_in_executor`
- Быстрый старт разработки
**Минусы:**
- В 2-3x медленнее Go по RPS
- Docker образ ~200-400 MB (Python runtime + зависимости)
- bcrypt блокирует event loop — нужен `run_in_executor` (решение #1004)
### Вариант B: Express/Fastify (Node.js 20+)
**Плюсы:**
- Единый язык с фронтендом (vanilla JS)
- `better-sqlite3` — синхронный, самый быстрый SQLite биндинг для Node.js
- Fastify ~24% быстрее FastAPI по RPS в независимых тестах
- Docker образ ~200-300 MB
**Минусы:**
- Нет опыта работы в команде
- `better-sqlite3` синхронный — блокирует event loop при долгих запросах (на практике приемлемо для simple INSERT)
- Дополнительное переключение контекста (JS для фронта, JS для бека)
### Вариант C: Go (net/http)
**Плюсы:**
- Компилируется в единый статический бинарь ~8-15 MB (простейший деплой)
- В 2-3x быстрее Python, ~2x быстрее Node.js
- Горутины — нативный concurrency без event loop ограничений
- Нет проблем с bcrypt (не блокирует горутины)
- Cross-compile: `GOARCH=amd64 GOOS=linux go build`
**Минусы:**
- Нет опыта работы в команде
- Более длительный онбординг
- `modernc.org/sqlite` (CGO-free): 10-100% медленнее нативного SQLite — компромисс для кросс-компиляции
---
## Решение
**Выбран Вариант A: FastAPI (Python 3.11+)**
---
## Обоснование
1. **Знакомость команды — главный фактор для минимального проекта.** FastAPI используется в Kin. Нет времени на онбординг в Go или Node.js для задачи, которая требует ~200 строк бэкенд-кода.
2. **Производительности FastAPI достаточно.** При нагрузке 300-400 одновременных запросов бутылочное горлышко — Telegram rate limit (20 сообщений/минуту в группу), а не скорость Python. SQLite WAL + `busy_timeout=5000` справится с 400 одновременными INSERT за ~400 мс (решения #1002, #1005).
3. **Pydantic даёт бесплатную валидацию** входных данных (user_id, timestamp, geo) без дополнительного кода.
4. **Размер деплоя приемлем.** ~300 MB Docker образ — не проблема для одного VPS сервиса.
5. **Вариант B отклонён:** нет опыта у команды, преимущество в скорости (+24%) несущественно при текущей нагрузке.
6. **Вариант C отклонён:** несмотря на превосходную производительность и минимальный деплой, отсутствие опыта в команде создаёт риск для проекта без аргументированной причины переходить на Go.
---
## Последствия
- Использовать `aiosqlite` для async SQLite операций (или `sqlite3` через `run_in_executor`)
- bcrypt (если понадобится в будущем) — только через `run_in_executor` (решение #1004)
- SQLite WAL обязателен: `busy_timeout=5000`, `synchronous=NORMAL` — вместе (решение #1005)
- Агрегатор Telegram: реализовать в Python как background task (asyncio) или через простой in-memory буфер с `asyncio.sleep`
- requirements.txt: `fastapi`, `uvicorn[standard]`, `aiosqlite`, `httpx` (для Telegram API)
- Переменные окружения: `BOT_TOKEN`, `CHAT_ID`, `DB_PATH` — читать из `.env` через `python-dotenv`