Привет, я Дмитрий Канаев, Backend Node.JS разработчик, представляю IT сообщество Работяги. В этом сообществе ты можешь поделиться своими проблемами в разработке и найти интересующие тебя вопросы из сферы IT.
Недавно в рабочем процессе я столкнулся с нестандартной задачей. Необходимо было в синхронном режиме запрос-ответ отдать информацию от сервиса, который работает только асинхронно. И в целом это задача не вызывала бы проблем, если бы этот конкретный сервис был построен по стандартной для подобного рода сервисов схеме.
Обычно, асинхронные сервисы работают следующим образом – мы формируем так называемый тикет, с параметрами запроса, который отправляем серверу. В тикете обычно указывается вызываемый метод и его параметры. После чего сервис возвращает нам в ответе идентификатор того тикета, который мы создали. Используя Long polling и полученный ранее идентификатор – дожидаемся ответа от сервиса и передаем полученные данные в ответ нашего запроса.
Но в данном случае сторонний сервис поддерживал только возврат ответа через ресивер. В данном случае на нашей стороне должен быть обязательно создан endpoint который будет принимать POST-запросы с ответами стороннего сервиса. Подобный подход часто применяется для сервисов с негарантированным временем ответа. И в целом это ок. Но в данном случае сервис гарантировал время ответа в пределах 15 секунд и обязательно ответ от него должен был отдаваться в синхронном режиме. Поэтому использование такого подхода вызвало недоумение😊
Ну что ж, проблема есть и ее как-то надо решать. В целом, картина решения родилась практически сразу. Если нам нужно чего-то дождаться – нужно обернуть это действие в промис, дабы не мешать EventLoop’у, и спокойно await’ить это событие.
Итак, рассмотрим реализацию этого функционала:
Функция getDataByValue является хэндлером нашего запроса, в котором мы получаем параметры, необходимые для обращения к стороннему сервису – param_value и id. На основании этих параметров мы формируем payload для внешнего сервиса, после чего вызываем функцию getExtDataByValue, которая отвечает за отправку сообщения внешнему сервису.
Результат выполнения запроса от внешнего сервиса мы ожидаем от функции resolveFunc, которой передаем идентификатор нашего запроса.
Рассмотрим эти две функции getExtDataByValue и resolveFunc – подробнее. Как и говорилось ранее, getExtDataByValue отвечает за отправку запроса внешнему сервису:
В данном случае обращение будет, для большей наглядности, направлено на сервис, эмулирующий поведение нашего целевого сервиса – поставщика данных.
Больший интерес для нас представляет функция resolveFunc – вся магия творится именно в ней😊
Как можно видеть, в ней используется сразу несколько интересных решений. Во-первых – функция возвращает промис, а значит мы можем дожидаться ее выполнения не блокируя EventLoop для другой работы. Во-вторых – мы используем встроенный функционал EventEmitter для обработки события получения ответа от внешнего сервиса. Ну и в третьих – это авторежект промиса по истечении 35 секунд – ведь внешний сервис отвечает нам асинхронно, и формально, хоть он и гарантирует время ответа, но ничего ему не мешает отложить нашу задачу в долгий ящик, и по сути мы бы никогда не дождались здесь ответа, что потенциально грозит утечками памяти в приложении. Поэтому мы не будем полагаться на удачу в этом вопросе и автоматически отклонять промис с ошибкой если ответ не пришел в течение 35 секунд.
Рассмотрим по подробнее ресивер, в который будет направляться ответ от стороннего сервиса:
Функционал максимально упрощен для улучшения понимания процесса. В данном случае «ручка» принимает от внешнего сервиса POST-запрос, после чего эмитит событие, с тем идентификатором запроса, который мы отправляли внешнему сервису.
Важным аспектом, о котором обязательно необходимо упомянуть, является то, что для того, чтобы заэмиченное событие корректно обрабатывалась в функции resolveFunc, необходимо чтобы эмитил и обрабатывал событие один и тот же инстанс EventEmitter’a. Сделано это в общем виде через require общего инстанса, определяемого в отдельном файле:
После подобных манипуляций, возвращаясь к функции getDataByValue, мы обрабатываем ответ и возвращаем его в response рамках одного и того же request, или – в случае если что-то пошло не так – выбрасываем ошибку.
Как видим, мы решили довольно нестандартную задачу, при этом полностью обойдясь возможностями языка JavaScript и платформы NodeJS.
Надеюсь, данная статья поможет вам в вашей повседневной работе и послужит каркасом для более сложных решений. Буду рад вашей обратной связи в комментариях к данной статье, а также в наших сообществах.
Ссылки на наши ресурсы – ниже: