Asosiy kontentga o‘tish
Innovialabs

aiogram 3 loyihasi arxitekturasi: routerlar, middleware va DI

Chuqur4 daq

Qisqacha

Handler’larni funksiyalar bo‘yicha routerlarga ajrating, biznes-mantiqni servislarda saqlang, DB sessiyasi va servislarni middleware orqali bering — shunda handler’lar yupqa va oson test qilinadigan bo‘ladi.

Qisqa javob

aiogram 3 dagi qo‘llab-quvvatlash oson bot to‘rtta g‘oyaga tayanadi:

  • Funksiyalar bo‘yicha routerlar: har bir yo‘nalish uchun bitta modul (start, katalog, buyurtmalar, admin), ular bitta joyda yig‘iladi.
  • Yupqa handler’lar: update’ni tahlil qiladi va javob beradi; mantiq Telegram haqida hech narsa bilmaydigan servislarda yashaydi.
  • Middleware orqali bog‘liqliklarni uzatish: DB sessiyasi va servislar global import emas, handler argumentlari sifatida keladi.
  • Tiplangan konfig bitta obyektda, dispatcher orqali uzatiladi.

Yangi funksiya — bu yangi router, biznes-mantiq esa botsiz test qilinadi.

Loyiha tuzilmasi

bot/
  __main__.py        # entry point: config, bot, dispatcher, start
  config.py          # settings from environment
  handlers/
    start.py
    orders.py
    admin.py
  middlewares/
    db.py            # session and services per update
  filters/
    admin.py
  keyboards/
  services/
    orders.py        # business logic, no aiogram imports
  db/
    models.py
    repositories.py
tests/

Asosiy qoida — bog‘liqliklar yo‘nalishi: handlers services haqida biladi, services esa db haqida, handler’lardan pastdagi hech narsa aiogram’ni import qilmaydi.

Konfig

Sozlamalarni ishga tushishda bir marta o‘qing va validatsiya qiling. pydantic-settings bilan yetishmayotgan o‘zgaruvchi foydalanuvchining birinchi so‘rovida emas, ishga tushishning o‘zida xato beradi.

from pydantic import SecretStr
from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env")

    bot_token: SecretStr
    database_url: str
    admin_ids: list[int] = []

SecretStr sozlamalar obyekti qayerdadir chop etilsa ham tokenni loglar va traceback’larga tushirmaydi.

Routerlar

Har bir handler moduli o‘z Router ini yaratadi va handler’larni unda ro‘yxatdan o‘tkazadi. Kirish nuqtasi ularni tartib bilan ulaydi.

# handlers/orders.py
from aiogram import Router
from aiogram.filters import Command
from aiogram.types import Message

from bot.services.orders import OrderService

router = Router()

@router.message(Command("orders"))
async def list_orders(message: Message, orders: OrderService) -> None:
    items = await orders.list_for_user(message.from_user.id)
    await message.answer(orders.format_list(items))

Tartib muhim: update filtrlari mos kelgan birinchi handler’ga boradi. Aniq routerlarni oldinroq, «hammasini ushlaydigan» handler’larni (masalan, «noma’lum buyruq») esa oxirgi routerga qo‘ying.

Middleware va bog‘liqliklarni uzatish

aiogram data lug‘atidagi qiymatlarni handler parametrlariga nomi bo‘yicha uzatadi. Middleware — DB sessiyasi va servislarni u yerga joylashning tabiiy joyi.

# middlewares/db.py
from typing import Any, Awaitable, Callable

from aiogram import BaseMiddleware
from aiogram.types import TelegramObject
from sqlalchemy.ext.asyncio import async_sessionmaker

from bot.services.orders import OrderService

class DbMiddleware(BaseMiddleware):
    def __init__(self, session_pool: async_sessionmaker) -> None:
        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
            data["orders"] = OrderService(session)
            return await handler(event, data)

Har bir update o‘z sessiyasini oladi, u handler tugagach yopiladi. Commit qayerda bo‘lishini aniq hal qiling: muvaffaqiyatli amaldan keyin servisda yoki handler’dan keyin middleware’da.

Ro‘yxatdan o‘tkazishning ikki turi:

Ro‘yxatdan o‘tkazishQachon ishlaydiNima uchun
dp.update.outer_middleware(...)har bir update’da, routingdan oldinloglash, throttling, bloklangan foydalanuvchilarni to‘xtatish
router.message.middleware(...)faqat handler filtrlari o‘tgandaaniq handler’larga kerak bo‘lgan ma’lumotlar

Kirish nuqtasi

# __main__.py
import asyncio

from aiogram import Bot, Dispatcher
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine

from bot.config import Settings
from bot.handlers import admin, orders, start
from bot.middlewares.db import DbMiddleware

async def main() -> None:
    settings = Settings()
    engine = create_async_engine(settings.database_url)
    session_pool = async_sessionmaker(engine, expire_on_commit=False)

    bot = Bot(settings.bot_token.get_secret_value())
    dp = Dispatcher()
    dp.update.middleware(DbMiddleware(session_pool))
    dp.include_routers(admin.router, orders.router, start.router)

    await dp.start_polling(bot, settings=settings)

asyncio.run(main())

start_polling ning nomlangan argumentlari umumiy ma’lumotlarga tushadi, shuning uchun istalgan handler yoki filtr settings: Settings ni so‘rashi mumkin.

Kirish filtrlari

Adminni tekshirish — har bir handler’dagi kod emas, butun routerga qo‘llanadigan filtr.

# filters/admin.py
from aiogram.filters import Filter
from aiogram.types import Message

from bot.config import Settings

class IsAdmin(Filter):
    async def __call__(self, message: Message, settings: Settings) -> bool:
        return message.from_user is not None and message.from_user.id in settings.admin_ids

# handlers/admin.py
router = Router()
router.message.filter(IsAdmin())

Testlar

Bog‘liqliklar argument sifatida kelgani uchun handler — oddiy async-funksiya. Servislarni test bazasida, handler’larni esa mock’lar bilan test qiling (bu yerda pytest-asyncio ishlatiladi):

from unittest.mock import AsyncMock, MagicMock

import pytest

from bot.handlers.orders import list_orders

@pytest.mark.asyncio
async def test_list_orders_replies():
    message = MagicMock()
    message.from_user.id = 42
    message.answer = AsyncMock()
    orders = MagicMock()
    orders.list_for_user = AsyncMock(return_value=[])

    await list_orders(message, orders)

    orders.list_for_user.assert_awaited_once_with(42)
    message.answer.assert_awaited_once()

Ko‘p uchraydigan xatolar

  • Handler’larga to‘g‘ridan-to‘g‘ri import qilingan global engine yoki sessiya: test qilish qiyin, ulanishlar oson «yo‘qoladi».
  • Handler ichidagi biznes-mantiq: uni admin panel, API yoki fon vazifasidan qayta ishlatib bo‘lmaydi.
  • Birinchi routerdagi «hammasini ushlaydigan» handler, u pastdagi hamma narsani tutib oladi.
  • router.message dagi middleware callback so‘rovlar uchun ham ishlaydi deb kutish.

Middleware va routing tafsilotlari — aiogram hujjatlarida.

Ko‘p beriladigan savollar

Shart emas. Ko‘pchilik botlar uchun sessiya va servislarni data ga qo‘yadigan middleware yetarli. DI-konteyner bog‘liqliklar ko‘p bo‘lsa, ularning hayot sikli turlicha bo‘lsa yoki xuddi shu servislar veb-API’da ham ishlatilsa o‘zini oqlaydi.

Bunda yordam beramiz

O‘zingiz shug‘ullanmasdan natija olishni istasangiz — biz har kuni shu bilan shug‘ullanamiz.

Loyihani muhokama qilish

Yana o‘qing

G‘oya yoki vazifangiz bormi? Bizga aytib bering

Ish kuni davomida javob beramiz va yechim taklif qilamiz. Konsultatsiya bepul.