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

5.5 KiB
Raw Permalink Blame History

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