Как переписать фронтенд нагруженного проекта и не потерять главного

в 11:16, , рубрики: Блог компании Яндекс, интерфейсы, команда яндекс.маркета, Разработка веб-сайтов, релизный цикл, управление проектами, управление разработкой, яндекс

Обложка статьи

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

Но не всё так радужно. Команда разработки мечтает о модных фреймворках, документации и идеальной архитектуре. Тимлиды честно пытаются ускорить разработку функционала, а новые члены команды неделями изучают велосипеды ваши лучшие практики построения информационных систем. Вы теряете темп в развитии сервиса, а конкуренты начинают догонять.

Знакомо? Если да, то вы в непростой ситуации. Она закономерно возникает в большинстве проектов, которые за годы своего существования накопили достаточный объём legacy.

Возможно, вас ждут и такие опасности:

  • поиск и найм новых специалистов усложнится;
  • квалифицированные сотрудники задумаются об уходе;
  • устаревшие технологии придётся поддерживать силами компании, расходуя время и деньги;
  • популярность сервиса начнёт падать.
    Если вы стремитесь построить сервис номер один на рынке, вам стоит следить за трендами в индустрии, планомерно обновлять технологический стек проекта, контролировать метрики производства и вовлечённость членов команды.

Недавно команда фронтенда Яндекс.Маркета также столкнулась с описанной ситуацией. У нас начало расти время разработки нового функционала, появились участки кода, которые могут сопровождать только «старички», нанимать и адаптировать новых членов команды стало сложнее и дороже. Чтобы справиться с этим, нам пришлось обновить свой технологический стек и переосмыслить часть процессов разработки и поставки нового функционала.

Как менялся фронтенд Маркета

Яндекс.Маркет существует уже 19 лет. Подумать только, пройдёт ещё немного времени, и он будет старше некоторых своих разработчиков. За эти годы фронтенд был переписан несколько раз.

XSLT

На заре развития сервиса и в последующие десять лет основой фронтенда Маркета была технология XSLT. На клиенте использовались JS-скрипты с рядом вспомогательных библиотек, БЭМ и набор портальных UI-компонентов Яндекса. Cерверная часть фронтенда работала на XScript — внутренней разработке Яндекса, базирующейся на XSL-трансформациях.

Главная страница Яндекс Маркета на XSLT

Вот так декларативно делались http-запросы к бэкендам на фронте Маркета в 2015 году:

<w.resource id="market-touch:models-top">
    <x:http>
        <x:guard type="StateArg">market:models-top.ids</x:guard>
        <x:method>getHttp</x:method>
        <x:param type="StateArg">market:config.some-host</x:param>
        <x:param type="String">GetCards?ids=</x:param>
        <x:param type="StateArg" as="Long">market:models-top.ids</x:param>
        <x:param type="String">&amp;type=micro</x:param>
        <x:param type="String">PopularModels?</x:param>
        <x:param type="String">&amp;region=</x:param>
        <x:param type="String">&amp;n=</x:param>
        <x:param type="StateArg" as="Long">market:models-top.count</x:param>
        <x:param type="StateArg" as="String">market:exp.guru-params</x:param>
    </x:http>
</w.resource>

А вот такие сообщения об ошибках получал разработчик:

<xscript_invoke_failed reason="UNKNOWN"
   object="Yandex/Example/Example :
      exampleRequestAuth /usr/local/www/assessor-ng/test.xml"/>

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

NodeJS

В 2016 году фронтенд Яндекс.Маркета перешёл на NodeJS. Это было непросто и потребовало много времени. Процесс был поступательным, в нём приняло участие большое количество инженеров Маркета. Использование NodeJS позволило нам работать с одним языком, решая задачу сразу в двух окружениях: на сервере и на клиенте. Появились полиморфные части кода и новая архитектура серверной части фронта. Был придуман и запущен процесс обучения команды. Мы стали эффективнее.

Изменения произошли и на клиенте. Появился новый JS-шаблонизатор Yate (Yet Another Template Engine). Он был разработан внутри Яндекса и наследовал некоторые идеи из XSLT. Использование этого шаблонизатора позволило заменить лишь инструмент, частично сохранив парадигму в умах разработчиков, которые до этого несколько лет работали с XSLT. Нам удалось ускорить обновление кодовой базы Маркета и сократить время на адаптацию новых сотрудников. Yate когда-то даже был выложен на GitHub и имеет свой playground. Попробуйте, если интересно.

Главная страница Яндекс Маркет 2015 год

Актуальный стек

В 2019 году технологический стек фронтенда Маркета обновился вновь. На сервере появилась статическая типизация (Flow), была доработана архитектура, вынесен общий код. Страницы разделились на новые виджеты и начали загружаться на клиент прогрессивно.

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

Виджеты на странице категории Яндекс Маркета

Что касается прогрессивной загрузки, то в классическом подходе html-страница в рамках одного GET-запроса компонуется (шаблонизируется) на стороне сервера, затем отдаётся браузеру. Происходит это по такой схеме:

Схема классической модели загрузки web-страницы

В модели, реализованной на фронте Маркета, сервер оперирует виджетами, которые отдаются на клиент чанками, сразу отображаются и инициализируются. То есть пользователь уже может что-то набирать в поисковой строке, пока страница ещё догружается. А в это время на сервере будут запрашиваться данные для других частей страницы. Важно, что это происходит в рамках одного обычного GET-запроса/ответа.
Такая загрузка страницы напоминает прогрессивную подгрузку JPEG. Отсюда и название.

В сравнении с классической (слева), прогрессивная загрузка (справа) выглядит так:

Схема прогрессивной модели загрузки web-страницы

Больше деталей можно найти в этом докладе.

Клиент мобильной web-версии Маркета мы перевели на React + Redux. Почему не на Vue.js? Мы устроили соревнование между двумя фреймворками. Две команды энтузиастов портировали страницу поисковой выдачи Маркета на обе технологии. По итогам внутренних бенчмарков и тестов React в нашем проекте показал себя лучше. К тому же он был более распространён в других проектах компании.

После завершения текущего обновления последуют новые. Что будет меняться — зависит от трендов в индустрии фронтенда. Поживём — увидим.

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

Как переписать фронтенд нагруженного проекта

Оглянувшись на недавнее обновление технологического стека в Маркете, приведу несколько советов.

Оцените, нужно ли вам обновляться именно сейчас. Стоит остановиться и ещё раз подумать, точно ли обновление — единственный выход, и не лечатся ли проблемы на сервисе как-то иначе.
В Маркете на этом этапе собирается рабочая группа из руководителей и опытных разработчиков. Задача такой группы — определить целесообразность обновления, выбрать перспективные технологии, предложить первичный план и стратегию.

Изучайте популярные технологии и тренды в индустрии. Стройте прототипы с использованием этих технологий в песочнице. Мы проделывали это с поисковой выдачей Маркета.

Стремитесь заранее понять, что подойдёт вашему сервису, а что нет. Не поддавайтесь хайпу, принимая решение внедрять что-то в проект. Смотрите на свои прототипы и результаты их тестирования. Оцените готовность команды работать с перспективной технологией. Если технология не подходит вашему сервису или команде, ищите альтернативы.

Зафиксируйте состояние ключевых метрик. Определите, какие метрики играют ключевую роль для вашего бизнеса, откуда и как сервис получает трафик, какие существуют внешние SLA, как распределяется нагрузка, когда стартуют маркетинговые компании и так далее. Что точно нельзя сломать? Следите за ключевыми метриками по ходу работ.

При обновлении фронтенда Маркета мы следим за всем перечисленным: используем Grafana, Graphite и другие инструменты для построения графиков, мониторинга и обнаружения деградаций на сервисе.

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

При переводе Маркета на React нам пригодился и альтернативный план. Одну часть сервиса мы портировали постранично, другую — блоками и виджетами.

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

Обновление Маркета мы начали с самых важных страниц: главной, поисковой выдачи, карточки товара. Это самые конверсионные, самые нагруженные и самые технически сложные страницы сервиса.

Учитывайте изменения окружающего контекста. Не старайтесь продумать детальный план на год вперёд. Параллельно с вашей работой сервис будет изменяться другими людьми. Будьте готовы обновлять и дополнять ваш план по ходу движения к цели.

Расскажите о своих планах смежным командам. Согласуйте план и стратегию с заинтересованными сторонами. Заранее определите ответственных за тот или иной участок функционала сервиса со стороны продукта. Будьте с ними и их планами на связи и в начале работ, и во время активной фазы.

Достаньте с высокой полки и изучите старые гайды по дизайну. Если их не было, переносите стили как есть (оптимизировать будете после), проводите дизайн-ревью обновлённой части сервиса. Только не пытайтесь проводить редизайн продукта при переходе на новую технологию. Переходите как есть.

При переезде на React у нас не было никаких дизайн-гайдов, только исходники. Макеты существующих страниц, которые нам удалось найти, не особо помогли. Было непросто. Чтобы ничего не упустить, мы проводили дополнительные дизайн-ревью на этапе разработки, писали скриншот-тесты, привлекали к проверке тестировщиков из смежных команд, так как они хорошо знают UI сервиса. После релизов мы внимательно следили за обращениями пользователей в службу поддержки Маркета и оперативно исправляли недочёты.

Заранее подготовьте и проверьте инфраструктуру. Научите сборку работать с новыми зависимостями и обновлённой файловой структурой проекта. Обновление технологического стека, скорее всего, потребует новой файловой структуры в проекте, нужно будет выносить общий код, появятся новые зависимости.

Мы продумали файловую структуру заранее, зафиксировали и описали её в документации. Перестроили и проверили сборку до начала работ по обновлению основного функционала.

Не игнорируйте тесты. Полезны будут все виды тестов: unit, интеграционные, e2e. Если можете, допишите тесты на ключевой функционал до старта работ по переходу на новую технологию. Если тестов нет и писать некогда — пишите по ходу работ. Рефакторинг без тестов возможен, но больше похож на вождение автомобиля с закрытыми глазами. Может и пронесёт.

Замеряйте клиентские метрики вашего ресурса: TTI, TTR, TTFB, TTLB и другие. Зафиксируйте показатели до начала работ и следите за ними по ходу продвижения к цели. Это поможет обнаружить деградации вовремя, сэкономит время и силы команды, сбережёт деньги бизнеса.

В Маркете установлены SLA на ряд клиентских метрик: TTI (time to interaction), TTR (time to render) и другие. При переходе на React мы неоднократно выходили за допустимые границы скорости работы нашего сервиса. Наличие SLA позволяло нам сразу замечать деградации, оптимизировать проблемный код до поставки в продакшн.

Воспользуйтесь A/B-тестированием при обновлении важных частей сервиса. Проверить технические усовершенствования в эксперименте только на срезе аудитории сервиса — хорошая идея. Если что-то пойдёт не так, расследовать деградации и чинить баги будет проще.

Во время портирования мобильной web-версии Маркета на React, мы провели почти 200 дней экспериментов. Однажды A/B эксперимент даже помог нам обнаружить и устранить нетривиальную утечку памяти в браузере. В экспериментах мы неоднократно видели, как незначительные, на первый взгляд, изменения могут негативно влиять на ключевые бизнес-показатели Маркета.

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

Придумайте заранее способ запуска новой технологии поверх старой и наоборот, если это возможно. Мы переписывали Маркет постранично, а каждую из страниц — повиджетно. Нам очень пригодилась возможность использовать React-виджеты в Yate-шаблонах.

Придумайте метрики, которые будут показывать прогресс в работе вашей команды. Это поможет поддержать мотивацию, продемонстрировать руководству движение к цели. Часто такую метрику придумать непросто, но сами величины вторичны, важно видеть прогресс. Мы, например, измеряли количество строк React-кода против Yate-кода.

Надеюсь, наш опыт будет вам полезен. Главное, наберитесь терпения, начинайте с самого сложного и не забывайте радоваться победам. Лёгкого обновления.

Автор: Александр Воронянский

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js