Как импортозаместить сервисы: Разработка корпоративного чат-бота с использованием React и библиотеки Urban Bot

Герман Никитин
Frontend - разработчик 

Всем привет! Меня зовут Герман Никитин, я frontend-разработчик в компании Центр Орбита. В веб-разработке я более 9 лет, из них 5 лет — с SPA на React.

Ни для кого не секрет, что сегодня чат-боты — это не просто вспомогательные программы для получения информации, а полноценные масштабные проекты, которые давно переросли свои изначально заявленные функции по получению информации. С их помощью вы можете записаться на прием к специалисту, составить маршрут, оплатить свои счета, заполнять интерактивные формы. Чат-бот сейчас ничем не отличается от классического веб-приложения.

Но когда доходит до разработки таких крупных проектов, перед нами всплывает большое количество проблем с выбором стека. В его основе лежит, в том числе, легкая поддержка и масштабируемость. Именно эти проблемы порождают многочисленное легаси, которое рано или поздно делает проект неподдерживаемым. Все это возникает из-за того, что большинство разработчиков выбирают классический способ разработки, а именно берут какую-то стандартную библиотеку, оборачивающую HTTP-запросы и на основе нее пытаются строить императивное приложение.

Также существует еще причина, которая сподвигла меня написать эту статью и поделиться своим опытом разработки чат-ботов. После глобальных геополитических событий, множество привычных нам сервисов покинуло Россию и тема импортозамещения встала очень остро. Компании, которые раньше пользовались целыми экосистемами зарубежных сервисов сейчас массово мигрируют на отечественные или дружественные сервисы.

В статье я расскажу о том, как мы импортозаметстили токсичные сервисы с помощью чат-бота и о малоизвестном способе разработки чат-ботов — React + декларативный подход, который основан на базе библиотеки Urban Bot, а также приведу примеры различных подходов разработки чат-ботов, и, разумеется, поделюсь своим опытом продуктовой разработки для импортозамещения сервисов Google.

Зачем чат-бот? В чем проблема?
Как и в большинстве IT-компаний, в Центр Орбита 95% сотрудников работают удаленно, при этом подбор около 70 человек ежемесячно, сотрудникам было достаточно тяжело поддерживать удаленную коммуникацию с HR и кадрами (КДП). Кроме этого по вышеупомянутым проблемам, имелось большое количество Google форм на разные тематики, начиная с заполнения ДМС после испытательного срока, заканчивая опросами настроения, на создание которых уходила значительная часть времени HR. Возникла необходимость импортозаместить сервис — создать единый быстрый сервис, который позволял бы получать оперативно информацию, не вовлекая лишний раз HR в шаблонные операции.

Что такое чат-бот?
Если вы знаете что такое чат бот, можете смело листать на след пункт.

Так что же такое современный чат бот? Если говорить вкратце, то это просто программа, которая занимается обработкой пользовательских сообщений в различных мессенджерах. Область применения таких программ — в основном работа с информацией от пользователя, ее обработка и систематизация, помощь пользователям в получении систематизированной информации.

Вы вводите некие данные в чат, бот их обрабатывает и на выходе вы получаете определенные данные от бота, которые соответствуют вашему запросу. Программа обрабатывает входные данные, и получается такая обычная рутинная работа. Но это только на первый взгляд.

Устройство классического императивного чат-бота
А вот если копнуть глубже, то в таких программах происходит огромное количество вычислительных процессов, чтений и записей из баз данных, взаимодействие с API и много другого. По сути чат бот — это набор простых функций и библиотеки, которая просто оборачивает HTTP запросы к API мессенджера в функции с готовыми аргументами и предоставляет различного вида подписки на определенные события, например, на событие отправки сообщений пользователем. И чаще всего все эти функции написаны императивно, то есть мы берем конкретные методы у библиотеки и управляем этими методами вручную, каждый раз вызывая одни и те же однотипные функции. Мы даем команду программе — слушай событие «А», используя вот этот метод с такими вот параметрами, отправь сообщение «Б», используя вот этот идентификатор сообщения, вот этот идентификатор пользователя и еще много разных параметров, в которых очень легко запутаться. Такой подход рабочий, но представьте себе какое количество лишнего кода придется написать, если стоит задача создать многофункционального бота.
В качестве примера на схеме ниже представлен типичный чат-бот, как например, мой Agile Cat.

У нас есть Библиотека для работы с API, которая создает инстанс бота с помощью встроенного конструктора и API ключа Telegram. В инстансе содержатся функции, для управления сообщениями, они же handlers, с помощью них мы, например, можем отправить сообщение, и listeners — это функции, которые вызывают коллбек обработки, когда происходит определенное событие.

Мы планировали назвать чат-бота «Орбимен». Юра появился случайно. Начали регистрировать доменное имя и выяснилось, что «Орбимен» занят. В процессе вспомнился Юрий Гагарин, и так как наша компания отчасти связана с космосом, мы решились.

Итого мы имеем пользователя, который отправляет команду. Слушатель эту команду видит и вызывает callback обработки. Далее информация как-то обрабатывается, например из БД по ID определяется какой это пользователь и выводит соответствующее сообщение, используя handler отправки из инстанса.

Особенности императивного подхода при разработке чат-ботов
Плюсы:

  • Меньше размер. Здесь по факту есть только библиотека и минимальный компилятор, например, встроенный в TypeScript. Да, вы можете навесить целую пачку дополнительных либ, но из коробки будут работать эти две вещи.
  • Больше контроля. Вы можете создать любое сообщение из чего угодно и как угодно это замиксовать. Сделать картинку, которая меню, которая выводится без превьюшки в календаре игнорируя все правила.

Минусы:

  • Следить, чтобы не потерялся ни один идентификатор. При императивном подходе мы сами контролируем все ингредиенты. Когда бот получает сообщение пользователя, вместе с ним приходят все его ID-шники: это ID самого сообщения, ID пользователя, ID чата, и т.д. В общем очень много служебной инфы, которую нужно грамотно передавать между функциями, и часто случается ситуация, когда какой-то из компонентов этой самой инфы затерялся из-за того, что ты просто не передал её на каком-то передаточном звене. Вот тогда возникает баг и приходится искать где ты что потерял.
  • Бойлерплейта много. Для тех кто не в курсе, это такой код, который нужен для того чтобы было удобно писать другой код. Весьма острая проблема множества проектов. Чтобы сформировать блок кнопок или блок сообщений разных типов, создавать колбеки в слушателях, следить за действиями пользователя, для всего этого нужны отдельные рудиментарные функции. Это очень сильно снижает качество кода.
  • Нужно управлять даже самыми простыми вещами. Чтобы удалить сообщение, после того, как его отправил бот, для перехода в другое меню, нужно писать отдельную логику и делать это в каждой функции, где вы используете отправку сообщений.
  • В связи с этим вытекает следующий минус — код становится очень сложно читаемый. При всей моей любви к чистому коду, при императивном подходе его организовать попросту не получается, можно лишь сделать его менее «плохим», но не хорошим. А это не наш метод. Мы на Орбите всегда стремимся к идеалу.

React и библиотека Urban Bot

Прогресс не стоит на месте, особенно когда дело касается автоматизации рутинных процессов. Команда отечественных разработчиков написала очень интересную библиотеку, суть которой заключается в том, чтобы использовать при разработке чат-ботов React. Что это нам даёт? Помимо того, что код становится чище, понятнее, приятнее и функциональнее:

- работа со state, все будет точно так же, как и в любом SPA приложении

- Middleware и state manager-ы

- Хуки и всё остальное, что есть в React

Но как же всё это работает? Я не автомеханик и как и многие, не очень люблю выражение “под капотом”, но здесь оно описывает действительную суть и будет в тему 🙂 По ссылке на Хабре, можно посмотреть API этой библиотеки, однако там не рассказано о том, как она работает. Я немного пообщался с разработчиками и постараюсь донести до вас основную суть максимально емко.

В React для отрисовки приложений используются отдельные библиотеки, мы привыкли видеть в вебе для этих целей библиотеку React-DOM, которая отрисовывает результат работы React на веб-страницу и называются такие библиотеки — renderers, т.е отрисовщики. Однако, такой подход можно использовать не только в вебе, а в любом приложении, которое позволяет отображать какой-то контент, например, в мессенджерах, где контентом являются сообщения и все что нужно сделать — это создать свой отрисовщик (renderer), похожий на React-DOM. Все отрисовщики, в том числе и React-DOM включают в себя так называемый “Реконсилятор” и рендер функцию. Реконсилятор — это модуль, задача которого смотреть и эффективно находить изменения элементов в дереве. Его экземпляр создаётся из библиотеки react-reconciler, которая официально поддерживается командой React. В него передается специальный объект с конфигурацией — Хост Конфиг. Это обычный JS объект, который описывает каким образом необходимо доставить изменения в среду, в которой происходит работа.

По сути создать свой отрисовщик — это создать функцию рендера, описать Хост-конфиг и передать его в экземпляр реконсилятора. Для создания различных нод (в нашем случае — сообщений) используется модуль Manager Bot — это сборник того, как в разных мессенджерах, в том числе и в Telegram, отправлять, удалять и изменять разные типы сообщений, включая текстовые, картинки, видео, голосования, гифки и т. д. Он создаётся с использованием API ключа конкретного мессенджера и является просто обёрткой над HTTP запросами.

Manager Bot активно используется в реконсиляторе, при описании реактовских событий, например добавлении, удалении или изменении дерева, в котором найдены определенные отличия, т. н Work In Progress Tree (WIP Tree). Все изменения дерева передаются отрисовщику, который в свою очередь определяет что нужно отрисовать, и куда это нужно отрисовать. Отрисовщик — это функция, которая получает параметры отрисовки, а именно — что и куда рисовать и запускает обновление контейнера, таким образом у нас стартует процесс отрисовки и дальнейших изменений.

Компонент на практике
Как выглядит на практике? Да примерно вот так. Здесь вы не встретите ничего сложного. В данном примере реализован компонент, который выводит текст ошибки, если в redux задиспатчилась ошибка. UseEffect следит, чтобы ошибка всегда была на месте. /notifications, в данном случае команда — это просто путь до компонента, если ее нет, то нас как бы возвращает в главное меню, через команду START. Навигация здесь, кстати, тоже очень простая, это вызов определенных команд, как /start. Написав команду компонента в чате бота, вы вызываете его рендер.

На просто сообщения Юра на данный момент не реагирует. Только на одну команду — START. Когда поступает команда /start, происходит запрос в БД Бота, где проверяется наличие записи по телеграм ID. Если запись находит, идет еще один запрос уже в REST сервис 1С с корпоративной почтой, где мы получаем всю информацию, в том числе рабочий статус. Если бот нашел запись в 1С, то в store приложения записывается вся актуальная по информация, в том числе по отпускам. Идентификатор наличия сессии — это записанный в store ID пользователя. Бот выводит приветственное персонализированное сообщение и устанавливает таймер активной сессии на 15 минут при маунте любого компонента внутри авторизованной зоны. По сути — это простой setTimeout. Если вы бездействуете, время истекает и бот запускает колбек на очищение store и редидектит на приветственное сообщение. Если вас в БД не находит, он предлагает зарегистрироваться, используя корпоративную почту.

Регистрация
Более же интересная регистрация в Юре. У нас нет в проекте как такового разделения на фронт или бэк. В чат ботах такого разделения просто не бывает. Логическое — да, но не физическое. Как я уже говорил, если бот не находит вас в его БД он вам предлагает зарегистрироваться и врубает вам соответствующий сценарий. Мы для регистрации используем корпоративную почту, так проще всего найти данные в 1С. Пользователю предлагается ввести почту, а далее в связи с тем что у нас в Орбите не одно юр. лицо, баз данных много. И бот, когда обрабатывает любой ваш запрос к 1С, идет по всем БДшкам с одним и тем же запросом, и если он находит хотя бы одну запись, данные формируются, сливаются в один объект, если их больше одного и записываются в стор. Далее идет генерация временного ОТП кода с соответствующей записью в БД бота. Если вы вводите верный код, ваша почта и ID в Telegram попадает сразу в БД бота. Если же вы ввели 3 раза неверный код, бот вас просто заблочит на полчаса.

Создание интерактивных форм
Планируя создание чат-бота мы хотели уменьшить человеческую активность в каких-то простых вещах и сделать универсальное средство сбора информации, импортозаместив сервисы. Как я уже упоминал ранее, история прошедшего года показала — многим иностранным сервисам доверять мы больше объективно не можем. Одна из основных задач Юры — возможность собирать информацию, с помощью интерактивных форм.

Сама интерактивная форма с точки зрения чат -бота — это последовательность трех типов сообщений:

1. Открытый вопрос — бот присылает вам сообщение с вопросом, а вы просто вводите ответ в текстовое поле сообщения

2. Сообщение с одним вариантом выбора — ряд кнопок, меню своего рода, вы нажимаете на кнопку и ответ засчитывается

3. Сообщение с множественным вариантом выбора. Тот же самый ряд кнопок, только здесь мы блокируем отправку ивента со всех кнопок, кроме кнопки сабмита. И при нажатии на кнопку выбора, сам текст кнопки меняется, к нему в начале добавляется галочка, такое интересное решение.

Data Flow формы
Чем примечателен Data Flow форм в боте — тем, что большую часть данных форма получает за вас из 1С-ки. То есть, все что обычно писали в гугл форме — это имя, фамилию, отдел, дату рождения и прочую шаблонную информацию, которая уже есть в базе, но по определенным причинам мы не могли ее автоматически ввести. Здесь же все шаблонные данные уже будут в форме и не придется вводить то что компании уже известно о сотрудниках. После сбора всех необходимых данных мы уже можем работать с этим объектом как угодно, в том числе сохранять и отправлять на подготовленный вебхук, в удобном для HR виде.

В боте присутствует пункт меню — уведомления. Это тоже отдельный компонент, который помогает собирать обратную связь. Туда прилетают различные формы, которые HR обычно просят заполнить. Здесь мы как и было сказано ранее уменьшаем тот самый человеческий фактор и разгружаем HR отдел.

Для организации работы нотификаций на сервере предусмотрен шедулер — это такая программа, которая может запускать определенные куски кода в определенный промежуток времени. В шедулере для этой цели предусмотрены джобы — это функции, которые описывают что и когда будет выполняться. Работают с помощью crontab. В боте у нас есть две джобы:

1. Первая джоба — очень простая, она каждый будний день, в 11 утра и в 6 часов вечера смотрит, есть ли в базе данных по идентификатору — строки с непрочитанными уведомлениями, и если есть, выводит в бота сообщение.

2. Вторая джоба занимается тем, что каждые 6 часов вносит в БД уведомлений те уведомления, которые подошли к сроку, или те, по которым выполняется определенное условие, например подошел конец испытательного срока. У нас есть дата приема на работу, а соответственно несложно вычислить, когда отправить нотификашку с формой ДМС.

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

Urban Bot позволяет создавать декларативные React приложения на базе чат — бота. Но здесь ситуация немного другая, чем в стандартном приложении, где есть браузер. Браузера нет и все действия обрабатываются не на устройстве пользователя, а на одном сервере, можно сказать, что устройство одно на всех и это стоит учитывать.

1. Необходимо позаботиться об уникальности состояния. На одном из первых тестирований бот дергал разные имена, разных людей и как бы перезаписывал состояние в зависимости от действий других пользователей, потому что состояние было одно на всех. Решили такую проблему, с помощью мемоизации. На начальном этапе создается кэш всего стора и внутренних middleware, однако в нашем случае мы решаем не проблему избыточных рендеров. Это позволяет сохранить ссылку на объект на этапе его создания и таким образом у каждого активного пользователя бота свой уникальный стор.

2. Необходимо внимательно следить за сессией. После запуска бота была серия таких интересных сообщений траблшутов, якобы кто-то сломал Юру, ничего не вызывается, кнопки не работают и вообще беда. Поэтому, для решения этой проблемы, мы сделали как раз вот такой таймаут, “а-ля” сессия, потому что из коробки бот просто переставал вам отвечать, когда сессия протухает.

3. Необходимо сделать грамотный менеджмент «зависимостей» в хуках. Здесь нужно понимать, что для браузера обновить компонент из дома гораздо проще, чем боту отправить сообщение, потому что задержка имеет место быть. Если в браузере рендер будет незаметен глазу, то тут процесс рендера несколько другой. Бот удаляет сообщение, а затем отправляет новое и этот процесс очень сильно режет глаза. Поэтому, при разработке приходилось внимательно смотреть за зависимостями, особенно в хуках, чтобы случайно не вызвать ненужный ре-рендер.

Разработка велась итерационно, поэтому мы уложились за месяц активных работ, хотя по факту времени на развертку окружения, тестирование и онбординг заняло гораздо больше, в районе 3-х месяцев.

Если у вас есть какие-то вопросы по реализации этого проекта, я всегда открыт к диалогу. Буду рад познакомиться и ответить на ваши вопросы. Задавайте их тут или в TG. Хорошего дня и спасибо за внимание!

0
Комментарии
-3 комментариев
Раскрывать всегда