[{"data":1,"prerenderedAt":217},["ShallowReactive",2],{"blog-ru-production-struktura-aiogram":3,"blog-counterpart-ru-production-struktura-aiogram":215},{"id":4,"title":5,"body":6,"cluster":192,"ctaType":193,"date":194,"description":195,"extension":196,"intent":197,"meta":198,"navigation":62,"ogImage":199,"path":200,"primaryKeyword":201,"readTime":202,"relatedCases":203,"relatedCourse":205,"relatedService":206,"seo":207,"slug":208,"stem":209,"tags":210,"updated":215,"__hash__":216},"blogRu\u002Fru\u002Fblog\u002Fproduction-struktura-aiogram.md","Production-структура проекта на Aiogram 3: handlers, services и конфигурация",{"type":7,"value":8,"toc":185},"minimark",[9,13,18,29,35,39,42,88,99,103,106,110,121,124,128,159,173,181],[10,11,12],"p",{},"Надёжная структура Aiogram 3 отделяет Telegram-слой от бизнес-логики: handlers принимают обновление, services выполняют действие, repositories работают с данными, а config отвечает за окружение. Это упрощает тестирование и не даёт платежам, FSM и уведомлениям переплестись в одном файле.",[14,15,17],"h2",{"id":16},"минимальная-структура","Минимальная структура",[19,20,26],"pre",{"className":21,"code":23,"language":24,"meta":25},[22],"language-text","app\u002F\n  bot.py\n  config.py\n  handlers\u002F\n    start.py\n    orders.py\n  services\u002F\n    orders.py\n  repositories\u002F\n    orders.py\n  keyboards\u002F\n  middlewares\u002F\ntests\u002F\n","text","",[27,28,23],"code",{"__ignoreMap":25},[10,30,31,34],{},[27,32,33],{},"bot.py"," собирает приложение и подключает роутеры. В handler не нужно помещать SQL-запросы и сложные расчёты. Он должен достать входные данные, вызвать сервис и выбрать ответ пользователю.",[14,36,38],{"id":37},"конфигурация-через-окружение","Конфигурация через окружение",[10,40,41],{},"Токен и ключи интеграций не должны попадать в код или репозиторий. Минимальная конфигурация проверяет обязательные значения на старте:",[19,43,47],{"className":44,"code":45,"language":46,"meta":25,"style":25},"language-python shiki shiki-themes github-light github-dark","from pydantic_settings import BaseSettings, SettingsConfigDict\n\nclass Settings(BaseSettings):\n    model_config = SettingsConfigDict(env_file=\".env\", extra=\"ignore\")\n    bot_token: str\n    database_url: str = \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Fbot.db\"\n","python",[27,48,49,57,64,70,76,82],{"__ignoreMap":25},[50,51,54],"span",{"class":52,"line":53},"line",1,[50,55,56],{},"from pydantic_settings import BaseSettings, SettingsConfigDict\n",[50,58,60],{"class":52,"line":59},2,[50,61,63],{"emptyLinePlaceholder":62},true,"\n",[50,65,67],{"class":52,"line":66},3,[50,68,69],{},"class Settings(BaseSettings):\n",[50,71,73],{"class":52,"line":72},4,[50,74,75],{},"    model_config = SettingsConfigDict(env_file=\".env\", extra=\"ignore\")\n",[50,77,79],{"class":52,"line":78},5,[50,80,81],{},"    bot_token: str\n",[50,83,85],{"class":52,"line":84},6,[50,86,87],{},"    database_url: str = \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Fbot.db\"\n",[10,89,90,91,94,95,98],{},"В репозитории хранится только ",[27,92,93],{},".env.example",". Реальный ",[27,96,97],{},".env"," добавляется на сервере или в секреты CI.",[14,100,102],{"id":101},"роутеры-вместо-большого-handler-файла","Роутеры вместо большого handler-файла",[10,104,105],{},"Группируй обработчики по предметной области: заявки, каталог, профиль, платежи. Роутер не должен знать, где физически хранится заявка. Это позволяет заменить SQLite на PostgreSQL без переписывания Telegram-слоя.",[14,107,109],{"id":108},"services-и-repositories","Services и repositories",[10,111,112,113,116,117,120],{},"Сервис отвечает на вопрос «что сделать», repository — «как прочитать или сохранить данные». Например, ",[27,114,115],{},"orders.create()"," проверяет бизнес-правила, а ",[27,118,119],{},"orders_repository.insert()"," выполняет запрос. Внешние API вызываются из отдельного gateway с timeout, повторными попытками и логированием request id.",[10,122,123],{},"Такой разнос особенно важен для оплаты. Webhook может прийти повторно, поэтому обработчик сначала проверяет idempotency key, затем обновляет заказ и только после этого выдаёт доступ.",[14,125,127],{"id":126},"что-проверить-перед-деплоем","Что проверить перед деплоем",[129,130,131,135,140,143,150,153,156],"ul",{},[132,133,134],"li",{},"запуск проходит без ручного импорта из рабочей директории;",[132,136,137,139],{},[27,138,93],{}," описывает все переменные;",[132,141,142],{},"polling и webhook не включены одновременно;",[132,144,145,146,149],{},"ошибки внешних API не теряются в пустом ",[27,147,148],{},"except",";",[132,151,152],{},"повторный update не создаёт дубль заявки;",[132,154,155],{},"есть health endpoint и понятный журнал событий;",[132,157,158],{},"критические handlers покрыты тестами.",[10,160,161,162,167,168,172],{},"Для первого проекта можно начать с ",[163,164,166],"a",{"href":165},"\u002Fru\u002Fcourses\u002Faiogram-start\u002F","бесплатного практикума Aiogram 3",", а затем перейти к ",[163,169,171],{"href":170},"\u002Fru\u002Fcourses\u002Ftelegram-bots-python\u002F","полному курсу по Telegram-ботам",", где есть платежи, Mini App, webhook, Docker и production-проверки.",[10,174,175,176,180],{},"Если нужна такая архитектура для реального продукта, ",[163,177,179],{"href":178},"\u002Fru\u002F#contact","опиши задачу в заявке",": достаточно указать основной сценарий, интеграции и предполагаемую нагрузку.",[182,183,184],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":25,"searchDepth":59,"depth":59,"links":186},[187,188,189,190,191],{"id":16,"depth":59,"text":17},{"id":37,"depth":59,"text":38},{"id":101,"depth":59,"text":102},{"id":108,"depth":59,"text":109},{"id":126,"depth":59,"text":127},"telegram-bots","course","2026-08-28","Как разложить Telegram-бота на Aiogram 3 по слоям, чтобы добавлять FSM, платежи и интеграции без единого огромного файла.","md","educational",{},"\u002Fog-image-v2.png","\u002Fru\u002Fblog\u002Fproduction-struktura-aiogram","структура проекта Aiogram 3","10 мин",[204],"fazer-cards-bot","telegram-bots-python","bots",{"title":5,"description":195},"production-struktura-aiogram","ru\u002Fblog\u002Fproduction-struktura-aiogram",[211,212,213,214],"Aiogram","Python","Архитектура","Telegram",null,"wLBL2iyeP47FfRO8Uk8ZGBpG1A8n0AXd3TaDYKFwuv4",1787941381476]