Initial commit: GM-Relay Telegram Bot
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# ADR-0001: Vertical Slice Architecture, Native AOT, BackgroundService и Aspire
|
||||
|
||||
## Status
|
||||
|
||||
**Accepted** — 2026-04-03
|
||||
|
||||
## Context
|
||||
|
||||
GM-Relay — Telegram-бот для автоматизации игровых сессий, развёрнутый на Raspberry Pi (ARM64) за NAT.
|
||||
Требования к стеку:
|
||||
|
||||
- **.NET 10 / C# 14** — целевой рантайм.
|
||||
- **Native AOT** — минимальный размер бинарника, мгновенный cold start на ARM64.
|
||||
- **Raspberry Pi 5** — ограниченные ресурсы (4–8 ГБ RAM).
|
||||
- **Long Polling** — Pi за NAT, webhook невозможен без проброса портов.
|
||||
|
||||
Необходимо выбрать: архитектурный паттерн, планировщик задач, ORM и оркестрацию.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Vertical Slice Architecture (VSA)
|
||||
|
||||
Каждый use case реализован как **вертикальный срез**: Command/Query record + Handler.
|
||||
Handler содержит всю логику (SQL, Telegram API, валидацию) — без абстрактных репозиториев и сервисных слоёв.
|
||||
|
||||
**Почему не Clean Architecture:**
|
||||
- Бот с 5–7 use cases не нуждается в 4+ проектах и абстракциях.
|
||||
- Все зависимости (Npgsql, Telegram.Bot) стабильны — заменять их не планируется.
|
||||
- VSA позволяет добавлять фичи без рефакторинга существующих.
|
||||
|
||||
### 2. BackgroundService + PeriodicTimer (вместо Quartz.NET)
|
||||
|
||||
Два триггера расписания (T-24ч: подтверждение, T-5мин: ссылка) реализованы через
|
||||
`BackgroundService` с `PeriodicTimer(TimeSpan.FromMinutes(1))`.
|
||||
|
||||
**Stateless-дизайн:** сервис при каждом тике делает SELECT к PostgreSQL, обрабатывает
|
||||
найденные сессии и обновляет статус в БД. При перезагрузке — ничего не теряется.
|
||||
|
||||
**Почему не Quartz.NET:**
|
||||
- Quartz.NET **не совместим с Native AOT** (reflection-based job loading).
|
||||
- Для двух простых WHERE-запросов Quartz — overkill.
|
||||
- BackgroundService нативно поддерживается .NET и AOT.
|
||||
|
||||
### 3. Npgsql + Dapper.AOT (вместо EF Core)
|
||||
|
||||
EF Core 10 **не совместим с Native AOT** (reflection-heavy query pipeline, dynamic IL).
|
||||
Npgsql ADO.NET — полностью AOT-совместим. Dapper.AOT использует source generators
|
||||
для compile-time генерации маппинга.
|
||||
|
||||
Миграции — **DbUp** (embedded SQL scripts).
|
||||
|
||||
### 4. Aspire 13 для оркестрации
|
||||
|
||||
Aspire обеспечивает:
|
||||
- Автоматический запуск PostgreSQL в dev-среде.
|
||||
- Service discovery и передачу connection strings.
|
||||
- OpenTelemetry (traces, metrics, logs) из коробки.
|
||||
- Aspire Dashboard для мониторинга.
|
||||
|
||||
### 5. Telegram.Bot 22.x + Long Polling
|
||||
|
||||
- Long Polling — единственный вариант для Pi за NAT.
|
||||
- Telegram.Bot поддерживает `System.Text.Json` source generators для AOT.
|
||||
|
||||
## Consequences
|
||||
|
||||
### Положительные
|
||||
|
||||
- **Один бинарник ~15–30 МБ** для ARM64 (Native AOT, self-contained).
|
||||
- **Мгновенный cold start** (~50ms) на Raspberry Pi.
|
||||
- **Простота**: каждая фича — один файл, один handler.
|
||||
- **Устойчивость к перезагрузкам**: stateless scheduler + PostgreSQL.
|
||||
|
||||
### Отрицательные
|
||||
|
||||
- **Ручной SQL**: нет автогенерации запросов, миграции пишутся руками.
|
||||
- **Нет LINQ-to-SQL**: все запросы — строки, ошибки только в runtime.
|
||||
- **DI без reflection**: все handlers регистрируются явно (без assembly scanning).
|
||||
|
||||
### Риски
|
||||
|
||||
- Dapper.AOT — относительно молодой проект; при проблемах — fallback на чистый Npgsql.
|
||||
- Telegram.Bot AOT-совместимость может потребовать кастомного `JsonSerializerContext`.
|
||||
@@ -0,0 +1,78 @@
|
||||
# GM-Relay — C4 Model
|
||||
|
||||
## Level 1: System Context
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title GM-Relay System Context
|
||||
|
||||
Person(gm, "Game Master", "Создаёт сессии, управляет расписанием игр")
|
||||
Person(player, "Player", "Подтверждает участие через inline-кнопки")
|
||||
|
||||
System(gmrelay, "GM-Relay Bot", "Telegram Worker Service на Raspberry Pi. Управляет подтверждениями, рассылает напоминания и ссылки.")
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API", "Long Polling. Сообщения, inline keyboards, callback queries.")
|
||||
SystemDb_Ext(postgres, "PostgreSQL", "Сессии, игроки, RSVP-статусы")
|
||||
|
||||
Rel(gm, telegram, "Команды бота (/newsession)")
|
||||
Rel(player, telegram, "Нажимает кнопки (✅ Буду / ❌ Не смогу)")
|
||||
Rel(telegram, gmrelay, "Updates (Long Polling)")
|
||||
Rel(gmrelay, telegram, "SendMessage, EditMessage, AnswerCallbackQuery")
|
||||
Rel(gmrelay, postgres, "SQL (Npgsql + Dapper)")
|
||||
```
|
||||
|
||||
## Level 2: Container
|
||||
|
||||
```mermaid
|
||||
C4Container
|
||||
title GM-Relay Containers
|
||||
|
||||
Person(gm, "Game Master")
|
||||
Person(player, "Player")
|
||||
|
||||
System_Boundary(pi, "Raspberry Pi 5") {
|
||||
Container(bot, "GmRelay.Bot", "Worker Service, .NET 10 AOT", "Long polling, обработка команд и callback queries, планировщик")
|
||||
ContainerDb(db, "PostgreSQL 16", "Database", "sessions, players, session_participants, game_groups")
|
||||
}
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API")
|
||||
|
||||
Rel(gm, telegram, "Commands")
|
||||
Rel(player, telegram, "Callback Queries")
|
||||
Rel(telegram, bot, "GetUpdates (Long Polling)")
|
||||
Rel(bot, telegram, "Bot API calls")
|
||||
Rel(bot, db, "Npgsql + Dapper.AOT")
|
||||
```
|
||||
|
||||
## Level 3: Component (GmRelay.Bot)
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title GmRelay.Bot Components
|
||||
|
||||
Container_Boundary(bot, "GmRelay.Bot") {
|
||||
Component(polling, "TelegramBotService", "BackgroundService", "Long polling loop, получает Updates")
|
||||
Component(router, "UpdateRouter", "C#", "Маршрутизирует Update → Handler по типу")
|
||||
Component(scheduler, "SessionSchedulerService", "BackgroundService", "PeriodicTimer(60s): T-24ч и T-5мин триггеры")
|
||||
Component(migrator, "DbMigrator", "DbUp", "SQL миграции при старте")
|
||||
|
||||
Component(confirm, "SendConfirmationHandler", "Feature", "Отправляет inline keyboard за 24ч")
|
||||
Component(rsvp, "HandleRsvpHandler", "Feature", "Обрабатывает ✅/❌, проверяет all-confirmed")
|
||||
Component(link, "SendJoinLinkHandler", "Feature", "Отправляет join link за 5 мин")
|
||||
}
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API")
|
||||
ContainerDb(db, "PostgreSQL")
|
||||
|
||||
Rel(polling, router, "Update")
|
||||
Rel(router, rsvp, "CallbackQuery rsvp:*")
|
||||
Rel(scheduler, confirm, "T-24h trigger")
|
||||
Rel(scheduler, link, "T-5min trigger")
|
||||
Rel(confirm, telegram, "SendMessage + InlineKeyboard")
|
||||
Rel(rsvp, telegram, "EditMessage + AnswerCallback")
|
||||
Rel(link, telegram, "SendMessage + user mentions")
|
||||
Rel(confirm, db, "SELECT/UPDATE sessions")
|
||||
Rel(rsvp, db, "UPDATE participants, SELECT counts")
|
||||
Rel(link, db, "SELECT confirmed players")
|
||||
Rel(migrator, db, "DDL migrations")
|
||||
```
|
||||
Reference in New Issue
Block a user