98 lines
5.5 KiB
Markdown
98 lines
5.5 KiB
Markdown
|
|
# 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`
|