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


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

Поделиться с друзьями
-->

Комментарии (40)


  1. jehy
    31.07.2017 13:24
    +4

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


    1. maksugr
      31.07.2017 14:07
      +2

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

      Node.js сам по себе многопоточный, так как использует разные треды для разных задач, а JavaScript-код приложения исполняется в одном потоке.


      1. yamalight
        31.07.2017 14:55
        +5

        В Node.js нету тредов, есть только child process'ы (если речь конечно не о внутренних тредах в libuv)


      1. rumkin
        31.07.2017 15:01
        +10

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


        1. ReklatsMasters
          01.08.2017 11:35
          +1

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


  1. shurupkirov
    31.07.2017 13:28

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


  1. rumkin
    31.07.2017 13:58

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


    1. dimskiy
      31.07.2017 14:23

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


      1. rumkin
        31.07.2017 14:53

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


        1. dimskiy
          31.07.2017 15:02
          +2

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


          1. rumkin
            31.07.2017 15:21

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


  1. vtulin
    31.07.2017 14:50

    А в Москве школа работать будет? Запишите видео уроков или только в живую?


    1. maksugr
      31.07.2017 16:34

      Школа будет работать только в Санкт-Петербурге и только вживую. Онлайна, к сожалению, не будет. Записи только для студентов Школы.


  1. tronus
    31.07.2017 16:24

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

    Чем то напомнил onEnterFrame в ActionScript. Только там MovieClip, а здесь process…


  1. ReZet
    31.07.2017 16:24
    +1

    А онлайн или запись будет?


    1. maksugr
      31.07.2017 16:35

      Онлайна, к сожалению, не будет. Записи только для студентов Школы.


      1. Buzzzzer
        31.07.2017 19:02

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


        1. Arnautka
          31.07.2017 19:21

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


          1. tronus
            31.07.2017 21:09

            Stepik пробовали?


        1. rumkin
          31.07.2017 19:23

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


        1. bro-dev
          01.08.2017 03:15

          Скорее всего с тем что 80 человек более менее адекватных и так сложно набрать, а если сказать что будет запись, то из адекватных гарантированно никто не придет.


      1. 1af75
        08.08.2017 18:50

        А созданное приложение электронного кошелька выложите для всеобщего обозрения? Любопытно посмотреть.


  1. QtRoS
    01.08.2017 00:43

    Можно поинтересоваться, для чего так массивно использовался XSLT (что про него несколько раз упомянули в статье)? В пользу чего от него отказались?


    1. maksugr
      01.08.2017 12:17
      +1

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


      1. domix32
        01.08.2017 16:17

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


      1. QtRoS
        01.08.2017 20:26

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


        1. domix32
          01.08.2017 20:51

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


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


  1. develop7
    01.08.2017 09:38

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


    1. KuzMaxxx
      01.08.2017 12:16

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


    1. maksugr
      01.08.2017 12:23

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


      1. develop7
        01.08.2017 23:24

        6 лет прошло, а так ничего и не поменялось.


  1. ReklatsMasters
    01.08.2017 11:32

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


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


    1. maksugr
      01.08.2017 12:51

      Конечно, nan используем. Нативные модули доставляют неудобство при миграции с одной версии Node.js на другую, так как в любом случае требуют пересборки и более тщательной проверки — это тормозит процесс миграции.


  1. virl
    01.08.2017 12:04
    -1

    Но ведь использовать на бекэнде динамически типизированный однопоточный язык с шизо-синтаксисом в котором ДАЖЕ НЕТ ЦЕЛЫХ ЧИСЕЛ — это дно.

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

    А все его асинхронно-ioшные плюшки давно есть в нормальных языках. В том же Kotlin есть и корутины, и жавовые сетевые либы на селекторах.


    1. maksugr
      01.08.2017 12:26

      Node.js мы используем только для агрегации данных от бекэндов и обработки пользовательских запросов — для этого Node.js отлично подходит. Вся математика и сложные вычислительные операции выполняются бекэндами на Java. Во избежание синтаксических ошибок в рантайме у нас используются линтеры.


  1. VikaVedy
    01.08.2017 12:06

    Эх, разработчики не живущие в СПБ пролетают.
    Это так трагично.
    Возможно ли всё же добавление курса на stepik в будущем?


  1. Odrin
    01.08.2017 12:46

    Именно в восьмой версии используется движок V8 c новым компилятором TurboFan

    Node v8.x.x поставляется с V8 5.8, TurboFan + Ignition включен по умолчанию только с версии 5.9


    1. maksugr
      01.08.2017 15:19

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


      1. iShatokhin
        01.08.2017 17:07

        8.3.0 c V8 5.9 должен был выйти на прошлой неделе, но в последней момент было решено сразу перейти на V8 6.0 (ждем релиз через несколько дней).


        Кстати, также подумывают к переходу на V8 6.1.


  1. igordata
    01.08.2017 17:51

    Серверная прослойка фронтэнда