baton/docs/adr/ADR-001-backend-stack.md

98 lines
5.5 KiB
Markdown
Raw Normal View History

2026-03-20 20:44:00 +02:00
# 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`