Всем привет! Меня зовут Влад Шатиленко, я продакт-менеджер в команде технической платформы Авито. Раньше я был разработчиком: начинал с аутсорса, затем работал в стартапе, где со временем перешёл в продакты. Сейчас в Авито занимаюсь развитием инструментов backend-driven UI — о них и пойдёт речь дальше.

Backend-driven UI — это подход, при котором интерфейс приложения формируется на бэкенде и доставляет без обновления мобильного клиента. В статье я расскажу, из какой проблематики вырос backend-driven UI в Авито, как развивался и почему его стоит рассматривать как инструмент со своими бонусами и ограничениями.

Статья будет полезна командам, которым важны скорость экспериментов, быстрые изменения интерфейса и кроссплатформенность.

Содержание

Чтобы перекрасить кнопку, нужно пройти весь SDLC, а это долго и дорого

Представьте, вы работаете в команде разработки, которая отвечает за экран рекомендаций. У продакта появилась гипотеза: если перекрасить текст с ценой и увеличить его размер, конверсия перехода в объявление вырастет. 

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

Казалось бы, зада��и разные, но у них есть кое-что общее — жизненный цикл разработки (SDLC). Упрощённо это выглядит так:

Всё начинается с гипотезы. Она прорабатывается и валидируется на кастдевах, UX-тестах и поведенческих тестах. 

Затем разработчикам передаётся спецификация или user story, на основе которой они пишут код мобильного приложения.

После этого мы собираем приложение и публикуем его в сторы: Google Play, App Store, RuStore.

В конце всё это попадает к конечному пользователю — в тот момент, когда он обновляет у себя приложение. 

У этого цикла есть несколько врагов:

Время. Сам по себе цикл устроен так, что даже самое простое изменение — например, перекрасить кнопку или срочно сделать rollback — требует прохождения полного цикла: разработки, сборки билда и выкатки.

Здесь возникает ещё один фактор — сторы и их модерация. На любом этапе может возникнуть задержка от дня до двух недель. И приходится разбираться, в чём именно проблема.

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

Инженерная нагрузка и поддержка. Выше я описал цикл для одной платформы, а у нас их как минимум три: Android, iOS и Web. Web можно опустить, потому что нет проблем со сторами и модерацией. 

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

Делаем неутешительный вывод: в продуктах, где важна скорость изменений, классический SDLC становится ограничением и замедляет развитие продукта. Параллельно растут издержки и нагрузка на команды. Я показал схему работы только для одной команды, а команд может быть три, десять, сто или даже больше. 

Есть разные пути решения. Например, WebView — встроенный в приложение браузер, который может быстро доставить изменения сразу на все платформы и отлично работать в кроссплатформенном формате, но не нативно. Другой вариант — React Native. Он решает задачу кроссплатформенной разработки, но проблему быстрой доставки изменений, к сожалению, не снимает.

Здесь на помощь приходит backend-driven UI. Его можно охарактеризовать тремя свойствами:

  • сервер управляет интерфейсом. На сервере хранится конфигурация как манифест интерфейса. Она передаётся на клиентское устройство, которое по заранее прописанным правилам рендерит экран и различные сценарии поведения;

  • мгновенная доставка изменений. Вся конфигурация находится на сервере, поэтому изменения можно публиковат�� сразу и доставлять на клиент без обновления приложения;

  • единое поведение на всех платформах. Backend-driven UI одинаково работает для всех платформ.

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

Вначале я упоминал, что работал в стартапе. У нашего мобильного приложения было относительно немного пользователей — состоятельные клиенты с портфелями в минимум несколько миллионов фунтов, которые через приложение следили за своими активами. Нам было неудобно просить клиентов обновлять приложение каждый раз, когда что-то менялось или могло сломаться. И уже тогда мы начали смотреть в сторону backend-driven UI — чтобы управлять тем, как отображается структура портфеля и другие данные. 

Тут еще больше контента

С backend-driven UI мы смогли перейти от релизов в сторах к мгновенной доставке

Теперь поговорим про Авито и то, как у нас устроен backend-driven UI. 

Наша вселенная сейчас состоит из двух продуктов:

  1. Beduin — язык программирования, который отвечает за рендеринг интерфейса, логику и общее поведение экранов.

  2. Bricks — платформа, с помощью которой можно настраивать экраны Авито без написания кода.

Мы стремимся ускоря��ь, упрощать и унифицировать. Ускоряем мы как раз за счёт свойств backend-driven UI и возможности моментальных релизов. Упрощаем за счёт платформы, в которой можно управлять интерфейсом без технических навыков. И унифицируем тем, что все компоненты согласованы с дизайн-системой Авито.

Теперь подробнее про Beduin. 

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

Beduin в Авито родился из потребностей продуктовой команды. Одна из команд решила сделать для себя инструмент, с помощью которого можно было бы быстро доставлять изменения на клиентские платформы. 

Beduin отвечал потребностям бизнеса, и другие команды начали подключаться и использовать его у себя. Спустя какое-то время возник вопрос, а как масштабировать Beduin в компании. И здесь мы столкнулись с ограничениями: чем сложнее экран, тем труднее с ним работать. Все компоненты были кастомными, со своей логикой, что делало систему сложной и не универсальной. 

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

В Авито есть принцип: всё начинается с потребностей пользователя.

Команда, которая отвечала за Beduin, подошла к задаче именно с этого принципа. Она провела исследования, собрала отзывы и задумалась, каким должен быть каноничный BDUI, который подойдёт всем. Новую версию разрабатывали по следующим принципам:

  1. В основе — компоненты дизайн-системы.

  2. Сделать возможность создавать многошаговые сценарии. Если проводить аналогию, первая версия — это, грубо говоря, HTML, а вторая — уже Progressive Web App.

  3. Разделить логику действий и форматирования данных. 

  4. Отделить данные от вёрстки.

  5. Сделать общий контейнер для связки всех компонентов.

Сейчас в проде у нас примерно 100+ сценариев на Android и iOS. На Web — примерно столько же. 

Beduin становится полноценным языком. 

Благодаря своей универсальности на нём можно делать не только душные экраны, но и экспериментировать. Например, можно реализовать Game of Life, Сапёр или Tetris. Так что если в приложении Авито вам вдруг прилетит Tetris — не удивляйтесь, это Beduin.

Но вернёмся к процессу. Раньше у нас была длинная и болезненная схема. Благодаря backend-driven UI мы сократили её как минимум на один шаг — нам больше не нужны магазины приложений. Мы можем собрать сценарий, разместить его в микросервисе и доставить на клиент, а пользователи сразу видят изменения. У нас появляется кроссплатформенность и один источник правды (SSOT, Single Source of Truth). Пользовательский сценарий может запустить команда без знания нативной разработки, либо если есть экспертиза только по одной платформе.

При этом SDLC никуда не девается — всё по-прежнему работает по классике. Инженеры продолжают писать код, билдить, пушить в репозитории и всякое такое.

Жми сюда!

Теперь интерфейсом могут управлять не только инженеры: от full-code — к low-code и no-code

Теперь представьте, что не только инженеры, но и аналитики, дизайнеры и редакторы могут управлять интерфейсом — менять текст, порядок блоков или параметры экранов без правок в коде. Для этого у нас есть визуальный конструктор Bricks. В его основе — виджеты, которые пишутся на Beduin. Они должны быть универсальными, чтобы их могли использовать где угодно. 

Есть сущность макет, которая собирает всё вместе: связывает виджеты, их логику, работу с данными, обработку событий и так далее. В нашем представлении экран — это просто набор виджетов. Это может быть текст, изображение, кнопка — неважно.

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

Единственное, что нужно, — интегрировать некий микросервис с Bricks и настроить доставку разметки на клиентское приложение. Вся магия происходит на этапе настройки шаблона и сборки сценария Beduin. Клиент же просто запрашивает готовую разметку и, используя рендер-движок, отображает результат.

Предположим, автогонщику Шарлю Леклеру надоело ездить за команду Scuderia Ferrari, он хочет продать свой контракт на Авито и попросил собрать ему экран. 

1. Берём из библиотеки компонентов готовый виджет или описываем собственный custom-виджет. Например, BricksButton — обычную кнопку. У виджета есть контракт и реализация, в которой мы описываем логику, положение и другие параметры.

2. Создаём шаблон и определяем, какие данные виджет будет получать на входе. Это могут быть любые значения: текст для рендеринга, ID объявления, которое будет дальше использоваться, параметры платформы, команды и так далее. 

Далее собираем виджеты на экране, конфигурируем их и можем посмотреть, как это будет выглядеть на проде в клиентском приложении. 

Но у текущего подхода есть проблема. Мы собрали экран для Шарля Леклера, но все данные в нём захардкожены, и для любого другого пользователя такой экран не имеет смысла. Поэтому можно добавить несколько этапов динамики. 

3. Добавляем управление отображением текста. Чтобы скрывать или показывать текст, используем механику зависимостей: настраиваем реакцию на нажатие кнопки или другое событие. Через этот же механизм мы можем настраивать что угодно, в том числе условия, при которых экран будет или не будет показан пользователю.

Но динамики всё ещё недостаточно. Для этого у нас есть дополнительный функционал — источники данных. Это абстракция над обычным API-запросом с входными параметрами, например ID или текст, и выходными данными. Фактически мы можем передать и получить всё, что допускает API.

4. Привязываем источники данных к шаблону и настраиваем так, чтобы входные параметры источника принимали параметр из шаблона. В этом кейсе мы передаём источнику ID объявления, а в ответ получаем данные этого объявления.

Рассмотрим пример экрана с каруселью изображений. В исходном варианте, независимо от переданного ID, отображается одно и то же содержимое — оно захардкожено. 

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

Теперь в зависимости от номера объявления нам в карусельку будут подгружаться соответствующие изображения. Этот подход применим ко всем полям. В финальном варианте экран настроен так, что отображаются только объявления с котятами, а объявление для Леклера не отображается, потому что параметры были переданы некорректно.

Получился финальный вариант:

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

В итоге упрощённая схема работы ещё больше сокращается. Нам больше не нужно билдить микросервисы и размещать в них Beduin-сценарии, мы настраиваем в админке, если нужно, добавляем код, и всё работает.

Когда backend-driven UI стоит применять, а когда — нет

Теперь — о плюсах и над чем стоит подумать.

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

При этом есть и ограничения, над которыми нужно думать: 

  • порог входа всё ещё достаточно высокий. Для более сложных кейсов по-прежнему требуется писать код; 

  • есть нюансы с тестированием: мы нажимаем кнопку, и изменения сразу попадают в прод, поэтому нужно health-чеки и quality-гейты; 

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

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

Как и любая технология, backend-driven UI требует инвестиций в плане R&D и в осмыслении того, как встроить его в существующую архитектуру. Я говорил, что мы срезаем этап доставки через сторы и App Store больше не нужен, но это не совсем так. Он всё равно необходим, потому что без него мы не можем обновлять сам движок. Пока что мы не умеем доставлять его в виде разметки.

Важно понимать, когда backend-driven UI стоит применять, а когда — нет.

Когда не стоит применять

Когда стоит применять

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

Есть сложные кейсы, которые проще и рациональнее решать нативно через интеграции с платформой, работу с камерой, NFC. Теоретически это можно реализовать и через backend-driven UI, но вопрос — в ценности таких усилий.

Есть сложная анимация: здесь нативная реализация будет проще.

Требуется офлайн-работа. При отсутствии интернета backend-driven UI работать не будет.

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

Нет компетенции хотя бы по одной из платформ.

Важна синхронизация работы логики на всех платформах и одновременный запуск фич без задержек.

Есть сложная логика, для которой принцип единого источника правды критичен.

Для сложных задач есть native, а для всего остального — BDUI. 

Ещё по теме

Кликни здесь и узнаешь

Спасибо, что дочитали до конца! Как вам такой подход и в каких сценариях вы бы стали его применять? Пишите мысли и вопросы в комментариях, буду рад пообщаться!

Узнать больше о задачах, которые решают инженеры AvitoTech, можно по этой ссылке. А вот тут мы собрали весь контент от нашей команды — там вы найдете статьи, подкасты, видео и много чего еще. И заходите в наш TG-канал, там интересно!