Почему Node.js в качестве основы фронтенда – это круто [обновлено]


    Накануне запуска школы 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-приложениях:


    1. HTTP-сервер на Node.js слушает входящие запросы от пользователей;


    2. он же обрабатывает данные из запроса: разбор cookies, парсинг тела post-запроса, логирование информации о запросе и т.п.;


    3. далее происходит перенаправление URL на логику его обработки:


      1. в процессе запрашиваются необходимые для страницы данные, происходит их агрегация и последующее выполнение бизнес-логики;


      2. сервер выполняет рендеринг HTML из собранных данных или формирует другой подходящий ответ клиенту (например, json, бинарный файл или редирект);

    4. сервер 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 занятий мы вместе разработаем серверное приложение электронного кошелька по управлению банковскими картами.


    Записывайтесь и почувствуйте себя одним из разработчиков целого сервиса Яндекс.Денег.

    Яндекс.Деньги
    Как мы делаем Деньги

    Комментарии 41

      +4

      Тут ваш разработчик говорит, что нода не однопоточная, а в статье утверждается обратное. Может, стоит определиться с консенсусом, а потом открывать школу?;)

        +2
        Это неточность формулировки. Спасибо, поправили!

        Node.js сам по себе многопоточный, так как использует разные треды для разных задач, а JavaScript-код приложения исполняется в одном потоке.
          +5
          В Node.js нету тредов, есть только child process'ы (если речь конечно не о внутренних тредах в libuv)
            +10

            Вообще принято считать ноду однопоточной, потому что JS выполняется одним потоком. Да и libuv хоть и использует разные потоки, но коллбеки вызывает последовательно только в основном. Так что можете переправлять обратно )

              +1

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

          0

          странный сбор данных
          https://yadi.sk/i/CVTHd10H3LaDW6

            0

            Немного оффтопа, а что в Яндексе никто не контролирует оформление материалов в социальных медиа?

              0
              Приветствую! Что именно вы имеете в виду?
                0

                Иллюстрация к статье как-то не "по гайдам" сделана, уж слишком самобытная. Мне всегда казалось, что у крупных компаний есть какая-то медиа политика, фирменный стиль для целевых ресурсов и отдел, куда можно притий и сказать мол нужны иллюстрации для таких-то целей.

                  +2
                  Ах вы об этом. Каждый блог — самобытен, и каждая статья тоже. Поэтому отличия — это хорошо
                    0

                    Не сказал бы, что в данном случае это идет на пользу.

              0
              А в Москве школа работать будет? Запишите видео уроков или только в живую?
                0
                Школа будет работать только в Санкт-Петербурге и только вживую. Онлайна, к сожалению, не будет. Записи только для студентов Школы.
                0
                Анализ ситуации вскрыл любопытную особенность. При передаче в process.nextTick функции обратного вызова она запустится в текущем такте асинхронного цикла после выполнения остального кода текущего такта, включая код отлова ошибок. Если внутри такой функции возникал Exception, его было не отловить, потому что process.nextTick вовсе не «следующий такт», а конец текущего.

                Чем то напомнил onEnterFrame в ActionScript. Только там MovieClip, а здесь process…
                  +1
                  А онлайн или запись будет?
                    0
                    Онлайна, к сожалению, не будет. Записи только для студентов Школы.
                      0

                      Простите, а с чем это связано ?

                        0
                        Кажется, что онлайн-трансляция того, как учатся другие, все равно не обеспечивает эффекта присутствия. Но мы хотели бы вынести на Хабр квинтэссенцию полезного, которое будет в Школе, поэтому сейчас думаем над другими форматами.
                          0
                          Stepik пробовали?
                          0

                          Удаленно хантить менее эффективно, имхо.

                            0
                            Скорее всего с тем что 80 человек более менее адекватных и так сложно набрать, а если сказать что будет запись, то из адекватных гарантированно никто не придет.
                            0
                            А созданное приложение электронного кошелька выложите для всеобщего обозрения? Любопытно посмотреть.
                          0
                          Можно поинтересоваться, для чего так массивно использовался XSLT (что про него несколько раз упомянули в статье)? В пользу чего от него отказались?
                            +1
                            XSLT — отличный декларативный шаблонизатор, позволяющий описывать логику преобразования данных в простой и удобной форме. Это дает возможность делегировать работу по обходу дерева и формирования результата роботу — ядру шаблонизатора. Но так как XSLT перестал поддерживаться, является медленным и обладает вербозным синтаксисом, мы перешли на аналогичные технологии на JavaScript, не потеряв в удобстве.
                              0

                              О, а не поделитесь ссылками на инструменты, если таковые имеются в открытом доступе?

                                0
                                Присоединяюсь к domix32, интересно, на что перешли. И в каком смысле XSLT перестал поддерживаться? Есть официальные пруфы, что формат забрасывают?
                                  0

                                  Перестал воддерживаться в том смысле, что никаких подвижек в улучшении механизмов, новых proposal'ов в последние годы не наблюдалось. HTML уже думает про v6, JS/ES про 7 версию думают, css о четвертой…
                                  И только хотел сказать, что у XSLT все довольно печально, как


                                  XSLT 3.0: became a W3C Recommendation on 8 June 2017.
                              0

                              Чем NodeJS в качестве серверной платформы лучше Erlang/Elixir?

                                0
                                В статье с названием «Почему X в качестве основы фронтенда – это круто», полагаю, должны описываться преимущества обозреваемой платформы, а не приводиться сравнительная характеристика с любыми доступными аналогами.
                                  0
                                  Во фронтенд разработке большинство знает JavaScript, а людей со знаниями Erlang на сервере и уверенными знаниями JavaScript на клиенте, к тому же готовых сверстать пару форм, отыскать крайне сложно. Node.js для нас лучше как минимум тем, что позволяет писать серверный код на том же языке, что и клиентский.
                                0

                                На самом деле турбофан по умолчанию включат в v8 6.0, когда он будет частью ноды. А произойдёт это уже очень скоро. Что примечательно, ABI не сломается и модули перекомпилировать не придётся.


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

                                  0
                                  Конечно, nan используем. Нативные модули доставляют неудобство при миграции с одной версии Node.js на другую, так как в любом случае требуют пересборки и более тщательной проверки — это тормозит процесс миграции.
                                  –1
                                  Но ведь использовать на бекэнде динамически типизированный однопоточный язык с шизо-синтаксисом в котором ДАЖЕ НЕТ ЦЕЛЫХ ЧИСЕЛ — это дно.

                                  У node.js выполнение в рантайме рушится от любого чиха и забытой запятой. В рантайме!

                                  А все его асинхронно-ioшные плюшки давно есть в нормальных языках. В том же Kotlin есть и корутины, и жавовые сетевые либы на селекторах.
                                    0
                                    Node.js мы используем только для агрегации данных от бекэндов и обработки пользовательских запросов — для этого Node.js отлично подходит. Вся математика и сложные вычислительные операции выполняются бекэндами на Java. Во избежание синтаксических ошибок в рантайме у нас используются линтеры.
                                    0
                                    Эх, разработчики не живущие в СПБ пролетают.
                                    Это так трагично.
                                    Возможно ли всё же добавление курса на stepik в будущем?
                                      0
                                      Именно в восьмой версии используется движок V8 c новым компилятором TurboFan

                                      Node v8.x.x поставляется с V8 5.8, TurboFan + Ignition включен по умолчанию только с версии 5.9
                                        0
                                        Действительно, компилятор Turbofan используется в V8 5.8 лишь частично, но полноценный переход на новый конвейер Ignition + Turbofan запланирован именно в рамках 8-й версии Node.js. Для подготовки к этому был даже задержан весенний релиз Node.js. В качестве планов команды Node.js звучало переключение на 5.9 уже летом.
                                      0
                                      Серверная прослойка фронтэнда
                                        0
                                        > прогонять серверный код через Babel — слишком большая головная боль при поддержке и сборке кода

                                        Почему это? То есть прогонять клиентский код через бабель, для поддержки V8 на старых браузерах это нормально, а делать то же по отношению к V8 в nodejs это «головная боль»?

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                        Самое читаемое