Накануне запуска школы Node.js от Яндекс.Денег я хотел бы рассказать чуть больше о том, почему именно эта платформа прижилась в нашем фронтенде.
Несколько лет назад в Яндекс.Деньгах назрела смена платформы для серверной прослойки фронтенда: имевшаяся была внутренней проприетарной разработкой и постепенно умирала от слабой поддержки и проседания скорости работы. Вместе с медленной работой в рантайме и отсутствием развития XSLT, на котором работало API, этот «черный ящик» с множеством ограничений настала пора заменить.
Под «фронтендом» мы понимаем не только выполняемый в браузере код, но и серверную прослойку по сбору данных и генерации HTML. Хорошей заменой для имевшейся логики стал Node.js.
Почему Node.js
Вместо традиционной модели параллелизма на основе потоков в Node.js используются принципы событийно-ориентированных систем – в сравнении с подходом «один поток на каждое соединение» код получается проще и быстрее. Node.js стал популярен благодаря обширному репозиторию NPM, внушительному сообществу разработчиков и возможности использовать JavaScript на клиенте, на сервере и для разработки инструментов. В общем-то, все эти преимущества понравились и нашей команде, поэтому выбор был очевиден.
2 сентября 2008 года был официально представлен первый публичный релиз открытого браузера Chromium, в ходе разработки которого также был создан JavaScript-интерпретатор V8. Они были оценены как очень быстрые и недалек был час, когда начали появляться идеи об использовании их на серверной стороне.
Так, в 2009 году появился проект Node.js — полностью самостоятельная платформа, включающая, кроме V8, встроенный сервер и базовый набор библиотек, а также предоставляющая полностью асинхронную работу с файлами и сетевыми устройствами с помощью библиотеки libUV.
Так как фронтенд Яндекс.Денег – это совокупность клиента и Node.js, в поле зрения фронтенд-разработчика попадает все то, с чем пользователь непосредственно взаимодействует или до чего может добраться. При этом «толстая» бизнес-логика живет в отдельных бэкенд-компонентах, которые предоставляют для Node.js-приложения публичное API на основе HTTP. Бэкенды ничего не знают про сущности страниц и интерфейсных компонент, зато манипулируют сущностями пользователей, платежей, настроек и т.п.
Из дополнительных бонусов перехода на новую технологию – не нужно переучивать разработчиков, и поддержкой инструментального кода теперь может заниматься не только один человек.
Как новый фреймворк устроился в нашем фронтенде
Для нашей платежной системы характерна как раз та нагрузка, для которой изначально разрабатывался Node.js: много операций I/O (входящие запросы и обращения к бэкендам) и мало работы в одном такте, без сложных синхронных вычислений. В итоге получается «живой» событийный цикл, в котором выполнение операций никогда не блокируется надолго.
Рассмотрим логику обработки пользовательских запросов в наших Node.js-приложениях:
-
HTTP-сервер на Node.js слушает входящие запросы от пользователей;
-
он же обрабатывает данные из запроса: разбор cookies, парсинг тела post-запроса, логирование информации о запросе и т.п.;
-
далее происходит перенаправление URL на логику его обработки:
-
в процессе запрашиваются необходимые для страницы данные, происходит их агрегация и последующее выполнение бизнес-логики;
- сервер выполняет рендеринг HTML из собранных данных или формирует другой подходящий ответ клиенту (например, json, бинарный файл или редирект);
-
- сервер Node.js выставляет общие заголовки ответа и отправляет ответ клиенту.
Для реализации всей логики обработки запроса у нас используется Express, популярный фреймворк Node.js. А для внутренних приложений используется Koa 2, который позволяет писать весь поток обработки запросов с помощью Async Functions – новой возможности JavaScript, значительно упрощающей написание асинхронного кода на JavaScript.
Более того, Koa 2, скорее всего, появится и на наших внешних приложениях, когда Node.js 8 станет LTS, а значит, пригодным для установки на продакшен серверы. Именно в восьмой версии используется движок V8 c новым компилятором TurboFan, который оптимизирует работу Async Functions и позволяет использовать их в продакшене.
Подробнее про V8 и новый оптимизирующий компилятор можно узнать из перевода статьи разработчика V8 Benedikt Meurer.
Статические же файлы, такие как js, css и картинки, сервером на Node.js не раздаются – для этого есть более подходящие веб-серверы вроде Nginx. Кроме того, в Nginx доступно более гибкое управление кэшированием. Поэтому на продакшене перед Node.js-приложением лучше ставить специальный веб-сервер.
Хотя Node.js выполняет JavaScript-код вашего приложения однопоточно, запускать процесс обработки всех пользовательских запросов на продакшене в один поток было бы опрометчиво, тем более при наличии множества ядер. Поэтому мы используем кластеризацию, то есть порождаем целый пул отдельных Node.js-процессов – воркеров – из основного мастер-процесса.
Метрики и графики
Как и все другие наши сервисы, фронтенд подключен к мониторингу, и все его ключевые показатели в реальном времени выводятся на многочисленные дашборды команды. Наблюдение за графиками особенно завораживает в дни релизов или, например, при конфигурации железа.
Для сбора метрик отлично подходят Graphite и Prometheus, а за наглядное отображение всего собранного отвечает Grafana.
Прямо в офисе у команд висят телевизоры с отображением графиков, интересующих их метрик.
Динамика ключевых показателей фронтенд-системы.
Свой дашборд есть и у фронтенд-команды – на него в реальном времени выводятся следующие графики:
-
время выполнения рендеринга HTML по всем страницам;
-
количество входящих запросов;
-
время обработки входящих запросов;
-
количество 5xx и 4xx статус кодов в ответах;
-
время выполнения исходящих запросов в бэкенды по всем вызовам;
- количество неуспешных исходящих запросов в бэкенды.
Зачем для всего этого отдельный телевизор? Во-первых, не всегда смотришь в почту и следишь за оповещениями, а тут все перед глазами и не нужно даже включать ноутбук. Во-вторых, можно с утра «залипать» в результаты вчерашнего труда.
С чем мы столкнулись
Выбор Node.js в качестве платформы для серверной прослойки помимо множества плюсов повлек за собой и определенное количество головной боли для разработчиков.
Например, таким большим приложениям, как наше, даже при наличии хорошего покрытия тестами и документацией, каждое обновление дается нелегко: постоянное нахождение в процессе изменений вытекает не только из желания разработчиков порадовать себя или улучшить свой продукт, но и из реалий современного мира JavaScript-разработки.
Node.js мы используем в связке с биндингами C++ кода в JavaScript, отказаться от которых в силу сложившейся инфраструктуры мы не можем. При каждом обновлении Node.js мы сталкиваемся не только со стандартными сложностями обновления по гайдам миграции, но и с обновлением наших биндингов для поддержки очередной версии Node.js: libxml, libxslt и наших внутренних C++ библиотек.
Вообще, зависимость от биндингов привносит дополнительные накладные расходы по поддержке, поэтому их лучше избегать – например, наши внутренние биндинги стараемся переводить на отдельные HTTP-сервисы.
Отдельная история – асинхронность Node.js, которую еще нужно уметь «готовить». Например, в какой-то момент мы заметили, что время от времени работающий воркер падал из-за ошибки бизнес-логики и перезапускался, хотя весь код логики в Express и Koa обернут в отлов ошибок и должен перехватываться. То есть бизнес-логика, описанная в обработчиках роута, никак не должна останавливать весь процесс Node.js, но у нас это все равно происходило.
Анализ ситуации вскрыл любопытную особенность. При передаче в process.nextTick функции обратного вызова она запустится в текущем такте асинхронного цикла после выполнения остального кода текущего такта, включая код отлова ошибок. Если внутри такой функции возникал Exception, его было не отловить, потому что process.nextTick вовсе не «следующий такт», а конец текущего.
Еще есть нюансы с ES2015: весь наш клиентский код проходит через транспиляцию в Babel, что позволяет нам использовать любые новшества языка и не только стандарта ES2015. Но Node.js не имеет такой хорошей поддержки и ряд возможностей в нем отсутствуют, а прогонять серверный код через Babel — слишком большая головная боль при поддержке и сборке кода. Таким образом, при написании JavaScript-кода нам необходимо всегда держать в голове окружение, где этот код будет исполняться, а также по-разному настраивать правила линтера.
Приятной вишенкой на торте трудностей можно считать «усложнившийся» процесс выбора модулей для использования. Безусловно, есть признанные лидеры в той или иной сфере применения, но и среди них можно обоснованно искать более подходящие для каждого конкретного случая. Да, количество logic-ревью возросло, но тем и хорош Node.js и NPM, что у нас есть широкий выбор открытых модулей и возможность не подстраиваться под технологию, а использовать удобную для нас.
Почему бы всем этим не поделиться
В процессе перехода на современные технологии мы, безусловно, сталкивались с трудностями, набивали шишки, но все это с лихвой окупилось бесценным опытом и преимуществами, не заканчивающимися исключительно на едином языке для клиента и сервера.
Накапливать все это с нуля долго и непродуктивно, поэтому наша команда решила запустить школу Node.js разработки и поделиться опытом. Обучение начнется 18 сентября 2017, а заявки на участие можно подавать уже сейчас.
За время обучения мы пройдем следующие темы:
-
веб-серверы: разновидности и принципы работы;
-
отладка и логирование в Node.js;
-
шаблонизаторы и серверный рендеринг HTML;
-
тестирование в Node.js: юнит-тесты, интеграционные тесты;
-
работа с базами данных в Node.js;
- потоки в Node.js.
На самом деле список длиннее, его можно посмотреть на странице школы. Но самая соль будет в практике – на протяжении 13 занятий мы вместе разработаем серверное приложение электронного кошелька по управлению банковскими картами.
Записывайтесь и почувствуйте себя одним из разработчиков целого сервиса Яндекс.Денег.
Автор: Яндекс.Деньги