scrpt .ru
· 7 min
Схема Backend-Driven UI: бэкенд отдаёт JSON, клиентский рендерер собирает интерфейс

Что именно переводили

Команда платформы Авито дважды переносила страницу объявления на Beduin — внутренний фреймворк Backend-Driven UI. В обычной нативной схеме бэкенд присылает клиенту только данные, а компоненты живут в коде приложения. В Beduin бэкенд отдаёт и данные, и описание самих компонентов в виде JSON; на клиенте остаётся рендерер, который принимает этот JSON и собирает экран. До перехода карточка была нативным React-компонентом в мобильной версии сайта (МАВ) и отдельными нативными реализациями на iOS и Android.

Мотив понятен из цикла разработки: любую правку функционала приходилось вносить в три кодовые базы, прогонять ревью и тестирование на каждой, разбираться с падениями и только потом запускать A/B-тесты. С Beduin изменение делается в одном месте и раскатывается на три платформы сразу, A/B-тест стартует везде одновременно. Опыт Авито показывает, что этот выигрыш реален, но сопровождается тремя статьями расходов: доработкой платформы под крупный экран, просадкой перформанса и нативными компонентами, которые всё равно надо писать трижды.

Первый подход: обёртки и упавший МАВ

Сначала карточку не переписывали целиком. В библиотеке Beduin для МАВ сделали компонент, который принимает идентификатор нативного JS-компонента и словарь: по идентификатору из словаря достаётся нужный блок и выводится на странице. Все блоки карточки — галерею, заголовок, цену — изолировали, убрали внешние параметры и условия, чтобы каждый вызывался независимо. Поверх собрали Beduin JSON, обращающийся к словарю. Так продуктовые вертикали получили возможность менять порядок блоков и убирать ненужные, а команда — способ проверить Beduin под нагрузкой карточки, с React и SSR, не останавливая всю разработку.

A/B-тесты обёрточной версии стабильно были красными. Чинили ошибки гидрации, запускали снова, тесты оставались красными. Затем упал сам МАВ. В четверг вышел новый релиз библиотеки Beduin для МАВ, в тот же день запустили A/B-тест карточки. В выходные начало расти потребление памяти, в воскресенье МАВ упал. Причиной оказался баг библиотеки: ошибка выбрасывалась снова и снова, возникал бесконечный цикл, память заканчивалась. Раньше баг не проявлялся, потому что подобной нагрузки на других экранах не было — карточка объявления стала первым местом, где его хватило, чтобы уронить весь сервис.

Как нашли расхождения блоков

Ошибки гидрации разложили по категориям карточки через отдельную разбивку в Grafana. Больше всего их оказалось в «Недвижимости» и смежных категориях. После правок тесты всё равно не сходились, и команда включила двойной рендер: нативная версия отрисовывалась как обычно, а под ней — невидимая Beduin-версия. В обеих собирали названия появившихся блоков и сравнивали; расхождения и лишние блоки писали в Clickstream. Режим держали включённым по 5–10 минут, только для проверки состава и порядка блоков, без замеров перформанса.

Это дало сойтись A/B-тестам. Обёрточную версию раскатили на 100% в категории «Товары»; небольшую просадку перформанса согласовали с профильной командой. Эта версия — нативные компоненты внутри, Beduin снаружи — до сих пор работает в «Товарах». Полная замена обёрток настоящими Beduin-компонентами так и не состоялась: планы изменились.

Второй подход: Kotlin DSL и нативная галерея

Четыре новые категории решили переводить на Beduin 2.0 с Kotlin DSL — языком на Kotlin для описания интерфейса, из которого генерируется Beduin JSON. DSL понадобился из-за сложных компонентов: чем больше в блоке зависимостей и логики, тем тяжелее описывать его прямо в JSON. В Kotlin DSL есть версионирование, подсветка синтаксиса, переиспользование и типы. Заодно переделали ответ бэкенда: убрали нативную обвязку с заголовком, ценой и прочими полями, оставив в основном Beduin JSON. Поля для аналитики, SEO и UX feedback перенести внутрь было сложно, поэтому они по-прежнему обрабатываются нативно.

Перед переписыванием провели двухнедельный хакатон: три разработчика (веб, iOS, Android) собирали proof of concept галереи. Каждый слайд задумали как Beduin-компонент, в который может прийти картинка, видео или произвольная Beduin-разметка. Полноценно на Beduin галерею собрать не получилось: не хватило возможностей фреймворка, для перехода между inline- и fullscreen-видом пришлось делать ручную синхронизацию и специальные интеракшены. Галерею оставили нативным компонентом — JS-компонент, который из Beduin JSON берёт только контракт и данные. Такой компонент нужно отдельно реализовывать на всех трёх платформах: галерея на МАВ заняла два-три месяца, примерно столько же уходит на Android и iOS.

Что достраивали в самой платформе

Запуск второй версии потребовал изменений на стороне команды Beduin. Из-за ошибок гидрации сделали отдельный рендерер Beduin Mobile Itemcard только с компонентами карточки; позже это заменили механизмом, который подключает в рендерер лишь используемые компоненты. Вертикали писали объёмные сценарии, JS страницы стал тяжёлым: кнопки отрисовывались раньше, чем загружались обработчики, и не реагировали на нажатие. Временно продлили показ скелетона до полной загрузки JS, затем лишний код убрали и вернули нормальную скорость.

Отдельная история — HTML-теги. Beduin поддерживал div и span, а вертикалям для веба нужны были обычные ссылки через a href вместо диплинков, метатеги для SEO, а также h1, p и другие теги для вёрстки. Поддержку добавляли в библиотеку Beduin для МАВ и в Kotlin DSL. Совместная доработка платформы под карточку заняла около трёх месяцев. Сейчас версия на Kotlin DSL запущена в категориях «Новое авто», «Б/У Авто» и «Подработка», в «Вакансиях» и «Путешествиях» идут A/B-тесты.

Как это читать команде перед переходом

Опыт карточки даёт несколько проверяемых ориентиров. Выигрыш от BDUI концентрируется там, где блоки простые и собираются из компонентов дизайн-системы, где часто нужны синхронные A/B-тесты на трёх платформах и где продуктовые команды хотят собирать свои сценарии из готовых кирпичиков. Расходы концентрируются на противоположном полюсе: возможности, доступные только нативно (камера, геолокация, переключение галереи в fullscreen), крупные экраны с высокой нагрузкой, где просадка перформанса заметна на продуктовых метриках, и любые сценарии, которые фреймворк пока не покрывает.

Практический критерий перед стартом — посчитать долю блоков экрана, которые реально ложатся на текущие возможности BDUI без доработки платформы, и заложить время на то, что останется нативным: каждый такой компонент умножается на число платформ. Ещё один вывод из ретроспективы: рендерер нельзя добавить на существующую страницу поверх старого кода. useEffect, хуки и JS-обвязка мешают работе рендерера и замедляют экран, поэтому страницу под BDUI собирают с нуля, оставляя только рендерер.

Обсудить проект

Остались вопросы? Напишите нам

Оставьте имя и телефон — перезвоним в течение часа, разберём задачу и предложим решение.

Также читайте нас в Telegram.