Написати найпростішого Telegram-бота під силу навіть джуніору в перший день знайомства з API. Проблеми починаються тоді, коли бот виходить за межі функції «відлуння» і починає запитувати в користувача дані, обробляти кроки авторизації, приймати оплату або інтегруватися з внутрішніми базами даних.
Якщо писати таку логіку лінійно, проєкт миттєво перетворюється на нескінченний ланцюжок конструкцій if/else або вкладених switch. Код починає залежати сам від себе, найменша зміна в одному кроці ламає сусідню гілку, а дебаг перетворюється на пошук голки в стозі сіна. Найгірше те, що такий «дикий» бот падає або плутає сесії, як тільки навантаження зростає хоча б до сотні одночасних користувачів.
Ми зіткнулися з цією архітектурною задачею на повну силу, коли проєктували стійке до відмов ядро для нашої платформи автоматизації та CRM-ботів. Рішення тут давно придумане в класичній теорії обчислювальних систем — це патерн FSM (Finite State Machine), або скінченний автомат.
Як «думає» правильний бот
Суть FSM максимально проста: користувач у кожен конкретний момент часу може перебувати лише в одному чітко визначеному стані. Бот не намагається вгадати контекст за текстом повідомлення — він дивиться на поточний статус юзера в базі.
Типовий ланцюжок станів для оформлення замовлення виглядає так: IDLE (очікування) → GREETING (привітання) → COLLECT_NAME (введення імені) → COLLECT_PHONE (введення телефону) → CONFIRM (підтвердження) → DONE (завершено).
Кожному стану відповідає суворо своя ізольована функція-обробник. Перехід на наступний етап можливий лише за умови виконання конкретного тригера (наприклад, юзер ввів валідний номер телефону — автомат перемкнув його на крок CONFIRM).
У чому головні плюси такого підходу для розробника?
Передбачуваний дебаг. Якщо клієнт скаржиться, що бот не реагує на відправку номера телефону, ви не перебираєте весь вихідний код. Ви точно знаєте, що баг сидить в обробнику стану
COLLECT_PHONE.Масштабованість без болю. Потрібно додати крок із вибором міста між введенням імені та телефону? Ви просто реєструєте новий стан і змінюєте вектор переходу у двох функціях. Решта коду навіть не «дізнається» про зміни.
Стабільність під навантаженням. Стани користувачів не потрібно зберігати в оперативній пам'яті інстансу бота (це перша причина падіння процесів). Вони легко виносяться у зовнішнє швидке сховище — наприклад, у Redis або легковаговий SQLite. Бот стає stateless: йому все одно, обробляти 10 людей чи 10 000. Якщо сервер перезавантажиться, користувач продовжить спілкування рівно з того символу, на якому зупинився.
Висновок
Архітектура завжди первинна. Спроба зекономити пару годин на старті й написати бота «на колінці» через кустарні глобальні змінні завжди призводить до технічного боргу, який простіше переписати з нуля, ніж рефакторити.
Якщо ви зараз проєктуєте складні сценарії автоматизації, інтеграції з CRM або кастомні воронки, одразу закладайте під капот FSM — це збереже ваші нерви та бюджет на підтримку.
Більше практичних розборів кодової бази, архітектурних патернів для Highload-проєктів та реальних кейсів автоматизації ми регулярно викладаємо в нашому технічному блозі — приєднуйтесь до спільноти.