Назад в блог
Инженерия
2 мин чтения
aiogramPythonDocker

Шаблон aiogram 3, к которому я возвращаюсь на каждом новом боте

Собрав один и тот же Telegram-бот с нуля три раза, я остановился и превратил это в шаблон: роутеры по функциям, сценарии FSM, middleware и настройка Docker, которая просто работает.

К третьему Telegram-боту я заметил, что каждый раз делаю одно и то же: копирую прошлый проект, удаляю обработчики, перенастраиваю конфиг и полдня воюю с одной и той же обвязкой, прежде чем написать хоть одну новую функцию. Это сигнал, что пора собрать шаблон, что я и сделал.

Первое, что чинит шаблон, это файл, который отращивает любой бот: один огромный модуль обработчиков, где вместе живут все команды, callback и сообщения. В aiogram 3 есть полноценный Router, поэтому я разделяю обработчики по функциям, и каждая владеет своим куском.

from aiogram import Router
from aiogram.filters import CommandStart

router = Router()

@router.message(CommandStart())
async def start(message):
    await message.answer("Welcome.")

Каждая функция получает свой router.py, а одно место подключает их все через dp.include_router(feature.router). Добавить функцию теперь означает «добавить папку», а не «отредактировать файл на 500 строк».

Второе исправление это многошаговые диалоги. Спросить у пользователя имя, потом возраст, потом подтвердить. Сделайте это на if и флагах, и сойдёте с ума. FSM в aiogram вместо этого делает шаги явными.

from aiogram.fsm.state import State, StatesGroup

class Register(StatesGroup):
    name = State()
    age = State()

@router.message(Register.name)
async def got_name(message, state):
    await state.update_data(name=message.text)
    await state.set_state(Register.age)
    await message.answer("How old are you?")

Состояние и его данные живут в Redis, а не в процессе. Это становится важным ровно в тот момент, когда вы запускаете больше одного воркера или перезапускаетесь ради деплоя. Тот же урок, который рано или поздно усваивает любой бэкенд, просто в костюме бота.

Всё сквозное уходит в middleware, а не повторяется в обработчиках. В шаблоне их три: throttle, чтобы один пользователь не мог долбить бота, инжектор сессии базы данных, чтобы обработчик получал готовую сессию, и i18n, чтобы ответы совпадали с языком пользователя. Обработчики остаются тонкими и занимаются одним делом.

class ThrottleMiddleware(BaseMiddleware):
    async def __call__(self, handler, event, data):
        if await self.too_fast(event.from_user.id):
            return
        return await handler(event, data)

Конфигурация это pydantic-settings, читающий окружение, поэтому в коде нет токенов, а один и тот же образ работает локально и в продакшене. И всё это Dockerfile плюс compose-файл с ботом, Postgres и Redis: пишете docker compose up, и оно запускается на ноутбуке или на свежем VPS, без археологии по README.

Ни одна из этих идей по отдельности не гениальна. Ценность в том, что они решены один раз и лежат в шаблоне, поэтому следующий бот начинается с «написать функцию», а не с «заново придумать обвязку».

Поделиться:Поделиться в Telegram
Ещё статьи