Разработка · 7 мин чтения

Разработка на Aiogram 3: чем отличается от предыдущих версий

Aiogram 3 вышел из статуса беты в конце 2023 года, и с тех пор я веду на нём все новые Telegram-боты - старые проекты на aiogram 2 переписываю только когда заказчик просит заметно расширить функциональность, потому что менять рабочий код без причины смысла нет. За полтора года практики набралось достаточно кейсов: боты для интернет-магазинов с интеграцией СДЭК и Т‑Банка, бот-помощник с RAG поверх базы знаний, десяток ботов для приёма заявок с сайтов на Tilda и WordPress. Разница с aiogram 2 не косметическая - поменялась архитектура регистрации хендлеров, система фильтров, работа с состояниями и подход к типизации апдейтов через Pydantic.

Роутеры вместо диспетчера - как теперь регистрируются хендлеры

В aiogram 2 все хендлеры вешались напрямую на объект Dispatcher через декораторы вроде @dp.message_handler. Работало нормально, пока бот маленький, но в проекте на 40-50 хендлеров диспетчер превращался в свалку импортов и циклических зависимостей - я сталкивался с этим на боте для магазина, где логика заказов, доставки СДЭК и оплаты через Т‑Банк жила в одном файле handlers.py на полторы тысячи строк.

В aiogram 3 хендлеры собираются в объекты Router, а роутеры потом подключаются к диспетчеру через include_router. Это позволяет резать бота на модули - отдельно роутер для оплаты, отдельно для FSM оформления заказа, отдельно для админки - и подключать их пачкой при старте.

# aiogram 2
from aiogram import Bot, Dispatcher, executor, types

bot = Bot(token="TOKEN")
dp = Dispatcher(bot)

@dp.message_handler(commands=["start"])
async def cmd_start(message: types.Message):
    await message.answer("Привет")

executor.start_polling(dp, skip_updates=True)

# aiogram 3
import asyncio
from aiogram import Bot, Dispatcher, Router
from aiogram.filters import Command
from aiogram.types import Message

router = Router()

@router.message(Command("start"))
async def cmd_start(message: Message):
    await message.answer("Привет")

async def main():
    bot = Bot(token="TOKEN")
    dp = Dispatcher()
    dp.include_router(router)
    await dp.start_polling(bot)

asyncio.run(main())

Бот больше не завязан на глобальный объект Bot внутри диспетчера - токен передаётся отдельно при старте polling или webhook. Это удобно, когда нужно поднять несколько ботов на одном коде для разных клиентов, меняя только конфиг.

Магические фильтры вместо lambda-условий

В aiogram 2 для нестандартных условий приходилось писать lambda-фильтры прямо в декораторе или регистрировать отдельные функции-фильтры через register. Читалось это плохо, а дебажить - ещё хуже.

Aiogram 3 принёс magic filter - объект F, который собирает условие в декларативном виде и проверяет поля апдейта без лишнего кода:

from aiogram import F
from aiogram.types import CallbackQuery

@router.callback_query(F.data.startswith("order_"))
async def process_order(callback: CallbackQuery):
    order_id = callback.data.split("_")[1]
    await callback.answer(f"Заказ {order_id} принят")

F работает не только с data колбэков, но и с текстом сообщений, с полями пользователя, с вложенными объектами апдейта - можно фильтровать по F.text.lower() == "да" или по F.from_user.id.in_(admin_ids). На практике это сократило количество вспомогательных функций-фильтров в проектах примерно вдвое - раньше под каждое нестандартное условие заводился отдельный файл filters.py.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

FSM в aiogram 3: состояния, контекст и хранилища

Машина состояний осталась концептуально похожей на aiogram 2 - те же StatesGroup и State, но интерфейс работы с ними стал асинхронным и явным. Вместо синхронного state.set_state() в aiogram 2 теперь везде await, а данные состояния хранятся через FSMContext, который прокидывается в хендлер как обычный аргумент.

from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext

class OrderForm(StatesGroup):
    name = State()
    phone = State()

@router.message(OrderForm.name)
async def process_name(message: Message, state: FSMContext):
    await state.update_data(name=message.text)
    await state.set_state(OrderForm.phone)
    await message.answer("Укажите телефон")

Хранилища состояний тоже переработаны: из коробки идут MemoryStorage для разработки и RedisStorage для продакшена, а интерфейс хранилища единый, так что переключение между ними - это одна строка в конфиге, а не переписывание логики. На ботах с формой заказа из нескольких шагов (имя, телефон, адрес, способ доставки через СДЭК) это снимает головную боль с потерей состояния при перезапуске процесса - раньше в aiogram 2 память терялась при каждом деплое, если не подключить Redis вручную с самого начала.

Билдеры клавиатур вместо ручной сборки rows

В aiogram 2 инлайн- и обычные клавиатуры собирались через types.InlineKeyboardMarkup(row_width=2) и метод .add(), куда кнопки добавлялись по одной, а логика переноса строк держалась в голове разработчика. В aiogram 3 для этого есть ReplyKeyboardBuilder и InlineKeyboardBuilder - кнопки добавляются списком, а раскладка по строкам задаётся отдельным вызовом adjust:

from aiogram.utils.keyboard import InlineKeyboardBuilder

builder = InlineKeyboardBuilder()
builder.button(text="Оплатить", callback_data="pay")
builder.button(text="Отменить", callback_data="cancel")
builder.adjust(2)

await message.answer("Выберите действие", reply_markup=builder.as_markup())

Удобно, когда количество кнопок формируется динамически - например, список товаров из корзины или доступных пунктов выдачи СДЭК по городу. В aiogram 2 такую динамику приходилось оборачивать в циклы с ручным подсчётом row_width, здесь builder сам раскидывает кнопки по строкам под нужное количество.

Мидлвари и передача данных в хендлеры

Мидлвари в aiogram 2 существовали, но были не особо гибкими - регистрировались через отдельный интерфейс, а данные из мидлвари в хендлер прокидывались через костыльные атрибуты объекта. В aiogram 3 мидлварь - обычный класс с методом __call__, который получает handler, event и словарь data, и может дополнить этот словарь чем угодно перед вызовом хендлера - сессией БД, объектом текущего пользователя, конфигом.

from aiogram import BaseMiddleware
from typing import Callable, Dict, Any, Awaitable
from aiogram.types import TelegramObject

class DbSessionMiddleware(BaseMiddleware):
    def __init__(self, session_pool):
        self.session_pool = session_pool

    async def __call__(
        self,
        handler: Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]],
        event: TelegramObject,
        data: Dict[str, Any],
    ) -> Any:
        async with self.session_pool() as session:
            data["session"] = session
            return await handler(event, data)

Хендлер потом просто принимает session как обычный параметр - aiogram сам подставит его из data по имени. Это ближе к dependency injection, чем к тому, что было в aiogram 2, и заметно сокращает число глобальных переменных и синглтонов в коде бота.

Что переписывать при миграции с aiogram 2 на aiogram 3

Если бот небольшой (до 15-20 хендлеров, без сложного FSM), перевод на aiogram 3 занимает день-два: меняются импорты, регистрация хендлеров переезжает в роутеры, lambda-фильтры превращаются в F. Если бот крупный, с интеграциями оплаты и доставки, миграция растягивается на 3-5 дней - там же обычно всплывают завязки на синхронный API старых версий, которые в aiogram 3 просто убрали в пользу async/await везде.

Отдельно стоит смена типизации: aiogram 3 перешёл на Pydantic 2, и если в проекте были собственные модели данных на Pydantic 1 для сериализации апдейтов, их придётся адаптировать под новые схемы валидации. Плюс поменялись пути импортов у части типов - types.Message в некоторых сборках теперь берётся из aiogram.types, но конкретные классы фильтров и middleware переехали в свои подмодули.

Параметр aiogram 2 aiogram 3
Регистрация хендлеров Через Dispatcher напрямую Через Router + include_router
Фильтры Lambda и кастомные классы Magic filter (F)
FSM Синхронные методы Асинхронный FSMContext
Клавиатуры add() построчно Builder + adjust()
Мидлвари Ограниченный интерфейс Полноценный DI через data
Типизация Pydantic 1 Pydantic 2

Если не хочется переписывать хендлеры вручную с нуля, у меня в библиотеке готовых скриптов есть заготовки на aiogram 3 под типовые сценарии - приём заявок, FSM-опросник, интеграция с оплатой. Что касается заказной разработки - веду боты на aiogram 3 под ключ и делаю миграцию существующих проектов с полным тестированием сценариев после переезда.

Автоматизация в мессенджере

Telegram-бот / Mini App

от 30 000 ₽

Подробнее →

Частые вопросы

Стоит ли переписывать старого бота на aiogram 3, если он работает на aiogram 2?

Если бот стабилен и не требует новых фич - переписывать ради самой миграции смысла нет, aiogram 2 никуда резко не денется. Смысл появляется, когда нужно добавить сложный FSM-сценарий, подключить Redis-хранилище состояний или упростить структуру кода на 30+ хендлерах - тогда миграция окупается снижением времени на дальнейшую поддержку.

Можно ли использовать aiogram 3 с вебхуками, а не поллингом?

Да, вебхуки поддерживаются нативно через интеграцию с aiohttp - Dispatcher умеет отдавать апдейты через веб-сервер без дополнительных библиотек. Для продакшен-ботов с высокой нагрузкой я обычно ставлю именно вебхуки, поллинг оставляю для разработки и небольших ботов с несколькими сотнями пользователей.

Совместим ли aiogram 3 с FastAPI, если бот встроен в существующий бэкенд?

Совместим, но напрямую не интегрируется - оба фреймворка асинхронные и работают на одном event loop, поэтому вебхук от Telegram можно принимать эндпоинтом FastAPI и передавать апдейт в диспетчер aiogram вручную через feed_update. Такую связку я собирал для проекта, где бот и админ-панель работали в одном процессе.

Сколько стоит разработка бота на aiogram 3 под ключ?

Телеграм-бот у меня начинается от 30 000 ₽ - это простой бот с приёмом заявок и базовым FSM. Интеграции с оплатой, CRM или доставкой (СДЭК, Т‑Банк) считаются отдельно и увеличивают стоимость в зависимости от сложности - точную оценку даю после короткой консультации от 3 000 ₽, где смотрю ТЗ и текущую структуру, если бот уже частично написан.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно