platform/ shadowed Python's stdlib platform module, breaking aiogram/aiohttp/multidict at import time. Renamed to sdk/ and updated all imports across core/, tests/, and adapter/telegram/.
6.4 KiB
Ресёрч: aiogram 3.x Architecture Review
Дата: 2026-03-30 Вердикт: APPROVED с двумя уточнениями
1. Структура проекта
Официальный пример multi_file_bot:
multi_file_bot/
bot.py
handlers/
common.py
...
Best practice для средних проектов (наш случай):
adapter/telegram/
bot.py ← Dispatcher + include_routers + polling/webhook
converter.py ← граница aiogram ↔ core/
states.py ← все StatesGroup
handlers/ ← по одному Router на модуль
keyboards/ ← InlineKeyboardBuilder фабрики
middleware.py ← DI + logging + rate limit
Оценка: наша структура соответствует стандарту. ✓
2. Middleware vs Converter
В aiogram 3.x эти два паттерна решают разные задачи и должны использоваться вместе.
| Middleware | Converter | |
|---|---|---|
| Назначение | Infrastructure | Бизнес-логика |
| Что делает | Логирование, DI, rate limit, сессия БД | aiogram Event → IncomingEvent |
| Когда вызывается | До и после хендлера | Внутри хендлера |
Правильная комбинация:
# middleware.py — только infrastructure
class DependencyMiddleware(BaseMiddleware):
def __init__(self, platform, store):
self.platform = platform
self.store = store
async def __call__(self, handler, event, data):
data["platform"] = self.platform
data["store"] = self.store
return await handler(event, data)
# handler — converter вызывается внутри
async def handle_message(message: Message, platform, store):
event = to_incoming_message(message) # converter
results = await dispatcher.dispatch(event, platform, store)
await send_results(message, results) # converter обратно
Оценка: наш converter.py — правильный паттерн. Добавить middleware.py для DI. ✓+
3. Dependency Injection
Стандарт aiogram 3.x — через middleware + data dict:
# Регистрация в bot.py
dp.message.middleware(DependencyMiddleware(platform=platform_client, store=store))
# Получение в handler (через type hint на имя ключа)
async def handle_message(message: Message, platform: PlatformClient, store: StateStore):
...
Альтернатива — через dp["key"] = value (Dispatcher workflow data):
dp["platform"] = platform_client # в bot.py
async def handler(message: Message, platform: PlatformClient): # aiogram сам находит по типу
...
Оценка: нужно явно добавить один из этих механизмов, иначе хендлеры не получат platform/store. ⚠️
4. InlineKeyboardBuilder
InlineKeyboardBuilder — рекомендуемый подход в aiogram 3.x. InlineKeyboardMarkup с вложенными списками считается устаревшим стилем.
# keyboards/chat.py
from aiogram.utils.keyboard import InlineKeyboardBuilder
def chats_keyboard(chats: list[ChatContext]) -> InlineKeyboardMarkup:
builder = InlineKeyboardBuilder()
for chat in chats:
builder.button(text=f"💬 {chat.name}", callback_data=f"chat:{chat.chat_id}")
builder.button(text="➕ Новый чат", callback_data="new_chat")
builder.adjust(1) # одна кнопка в строку
return builder.as_markup()
Оценка: использовать InlineKeyboardBuilder везде. ✓
5. F-фильтры (MagicFilter)
aiogram 3.x MagicFilter (F) — стандарт вместо ручных проверок в хендлерах:
from aiogram import F
# Вместо if message.text == "/start" внутри хендлера
router.message.register(start_handler, Command("start"))
# Фильтр по типу вложения
router.message.register(voice_handler, F.voice)
router.message.register(photo_handler, F.photo)
# Фильтр по состоянию
router.message.register(handle_name_input, OnboardingState.waiting_for_name)
# Callback фильтр
router.callback_query.register(confirm_handler, F.data.startswith("confirm:"))
Оценка: использовать F-фильтры при регистрации роутеров — чище, чем if/else в хендлерах. ✓
6. Сцены (Scenes) — новинка aiogram 3.x
aiogram 3.4+ ввёл Scene как улучшенный FSM для сложных диалогов:
from aiogram.fsm.scene import Scene, on
class OnboardingScene(Scene, state="onboarding"):
@on.message.enter()
async def on_enter(self, message: Message):
await message.answer("Как зовут твоего агента?")
@on.message()
async def on_name(self, message: Message, state: FSMContext):
await state.update_data(agent_name=message.text)
await self.wizard.goto(OnboardingScene2)
Оценка: Scenes — опциональное улучшение для онбординга. Классический FSM через StatesGroup тоже корректен и проще для понимания. Использовать StatesGroup для прототипа, Scenes — в будущем. ✓
Итог
| Решение | Статус |
|---|---|
| Router-based архитектура, один Router на модуль | ✅ Стандарт |
| converter.py как граница aiogram ↔ core/ | ✅ Правильный паттерн |
| InlineKeyboardBuilder в keyboards/ | ✅ Рекомендуется |
| SQLiteStorage для FSM | ✅ Стандарт для MVP |
| Нужно добавить: DependencyMiddleware | ⚠️ DI без него не работает |
| Нужно добавить: F-фильтры при регистрации | ⚠️ Иначе проверки в хендлерах |
Архитектура одобрена. Два уточнения (middleware.py и F-фильтры) небольшие и органично вписываются в текущую структуру.