Когда Telegram-бот начинает выполнять сразу несколько задач, архитектура быстро усложняется. В одном месте оказываются команды, платежи, уведомления, интеграции и служебная логика. На первых этапах это кажется удобным: один проект, один сервер, один webhook.
Но по мере роста системы такой подход превращается в источник проблем. В моем случае один Telegram-бот должен был взаимодействовать сразу с тремя отдельными сервисами. Каждый отвечал за свою область, развивался независимо и имел собственный жизненный цикл.
Объединять их в единственное приложение не хотелось: это увеличило бы связанность, усложнило развертывание и сделало изменения рискованными.
Решением стал отдельный webhook-router - небольшой сервис, который принимает все входящие обновления от Telegram и перенаправляет их нужному обработчику. При этом наружу остается только одна точка входа, а внутри сохраняется четкое разделение ответственности.
Почему единый монолит начал мешать
На старте проекта монолит выглядел вполне разумно. Все обработчики находились в одном репозитории, использовали общую конфигурацию и запускались одной командой. Чтобы добавить новую возможность, достаточно было создать еще один модуль и подключить его к существующему приложению. Однако со временем преимущества начали исчезать.
Разные части системы стали требовать разных зависимостей, настроек и темпов выпуска.
Изменение в одном обработчике приходилось проверять вместе со всем приложением. Даже небольшая ошибка в новом функционале могла повлиять на уже работающие команды бота. Кроме того, монолит оказался неудобен с точки зрения масштабирования.
Один сервис обрабатывал короткие пользовательские команды, другой выполнял тяжелые операции, третий работал с внешними API. При общей точке запуска невозможно было гибко распределить ресурсы: приходилось масштабировать весь проект целиком, даже если нагрузка росла только на одном направлении.
Три сервиса с разными задачами
Первый сервис отвечал за основной пользовательский сценарий. Он обрабатывал команды, реагировал на текстовые сообщения и возвращал результаты непосредственно в Telegram.
Для него были важны минимальная задержка и предсказуемое поведение. Второй компонент занимался операциями, связанными с оплатой. У него были собственные правила безопасности, отдельные переменные окружения и интеграция с платежной системой.
Смешивать такую логику с обычными командами бота было не лучшей идеей. Третий сервис выполнял фоновые и интеграционные задачи. Он взаимодействовал с внешними системами, мог обрабатывать запросы дольше остальных и иногда требовал повторных попыток.
В монолитной архитектуре подобные особенности неизбежно начинали влиять на соседние функции.
Как появился webhook-router
Telegram отправляет обновления на URL, указанный в настройках webhook. Важный момент заключается в том, что для одного бота используется одна такая точка. Нельзя просто назначить три разных адреса и ожидать, что Telegram сам выберет подходящий сервис.
Поэтому между Telegram и внутренними приложениями появился маршрутизатор. Он принимает входящий HTTP-запрос, анализирует его содержимое и определяет, куда передать событие. Для Telegram при этом ничего не меняется: бот по-прежнему работает с одним публичным адресом.
Схема стала выглядеть следующим образом:Telegram → webhook-router → нужный сервис. Маршрутизатор не содержит бизнес-логику.
Его задача ограничена приемом обновления, проверкой базовых условий, выбором назначения и передачей запроса дальше. Благодаря этому он остается компактным и не превращается в еще один монолит.
Маршрутизация по типу события
Основой маршрутизации стал анализ структуры Telegram Update. В зависимости от сценария обновление может содержать текстовое сообщение, callback от inline-кнопки, команду, данные платежа или другой тип события.
Например, обычные пользовательские сообщения можно направлять в основной сервис, callback-запросы - в компонент, отвечающий за интерактивные сценарии, а платежные события - в отдельное приложение с усиленными проверками.
Если обновление связано с внешней интеграцией, его можно передать третьему сервису. На практике маршруты лучше строить не только по наличию конкретного поля, но и по понятным правилам.
Сначала определяется тип события, затем применяются дополнительные условия: команда, префикс, идентификатор чата или иной признак.
Такой порядок упрощает отладку и снижает вероятность того, что одно событие будет ошибочно перехвачено неподходящим обработчиком. Еще один важный принцип - наличие маршрута по умолчанию. Если обновление не соответствует известным правилам, router должен обработать его предсказуемо: передать в основной сервис, записать информацию в журнал или вернуть корректный ответ.
Молчаливое игнорирование неизвестных событий затрудняет поиск ошибок.
Зачем понадобился единственный 301
Отдельная задача возникла из-за адреса webhook. На раннем этапе использовался один URL, а после появления маршрутизатора потребовался другой. Менять настройки Telegram хотелось без риска пропустить события или нарушить работу бота.
Для этого старый адрес начал возвращать HTTP 301 Moved Permanently и перенаправлять запросы на новый endpoint. Такой переход позволил сохранить обратную совместимость и постепенно перевести систему на обновленную схему. Важно понимать, что перенаправление - не универсальное решение для webhook.
Поведение зависит от клиента, версии API и конкретной реализации отправителя.
Поэтому 301 нужно проверять в реальном окружении, а не считать гарантированно безопасным только потому, что стандарт HTTP его допускает. В конечной конфигурации Telegram был настроен непосредственно на новый адрес router. Старый URL оставили лишь как временный переходный слой.
Это принципиально надежнее, чем постоянно рассчитывать на редирект при доставке важных обновлений.
Что пришлось учесть на практике
Первое требование - корректно отвечать Telegram. Если downstream-сервис работает долго или временно недоступен, router все равно должен контролировать время ответа.
Длительные операции лучше передавать в очередь либо запускать асинхронно, чтобы внешний запрос не зависел от всей внутренней цепочки. Второй момент - защита от повторной обработки.
Telegram может повторить доставку обновления, если не получил ожидаемый ответ. Поэтому каждый сервис должен учитывать возможные дубликаты, а router - не изменять тело запроса и не терять идентификаторы событий. Третья задача - безопасность. Публичный endpoint необходимо закрыть от случайных и вредоносных запросов.
Для этого применяются секретный путь, проверка заголовков, ограничение по сетевым правилам и валидация входных данных.
При этом сам маршрутизатор не должен доверять полям запроса без проверки. Не менее важны журналирование и наблюдаемость. Для каждого события полезно сохранять время получения, выбранный маршрут, статус ответа и идентификатор обновления.
Если один из сервисов перестает отвечать, по этим данным можно быстро определить, где именно возникла проблема. В результате webhook-router стал небольшим, но значимым инфраструктурным слоем. Он позволил оставить для Telegram одну точку входа, а внутри разделить систему на три самостоятельных сервиса.
Каждый компонент получил собственные зависимости, настройки и процесс развертывания, не вмешиваясь в работу соседей. Главный вывод оказался простым: единый webhook не означает, что вся логика должна находиться в одном приложении.
Может быть интересно: "Семейная ипотека" на вторичное жилье: где доступна и как оформить однушку?
Между внешним API и внутренними сервисами можно поставить тонкий маршрутизатор, который решает задачу распределения запросов без превращения в очередной монолит. Такой подход особенно полезен, когда проект постепенно растет, команды начинают работать независимо, а разные функции требуют разной инфраструктуры.
Главное - не перегружать router бизнес-правилами, заранее продумать обработку ошибок, учитывать повторы и использовать редирект 301 только как временный механизм миграции.