Привет, меня зовут Михаил Шевченко. В Авито я проектирую и разрабатываю backend low-code-платформы Bricks. Мой профессиональный путь — это разнообразные роли, от разработчика до архитектора. Я fullstack, обладающий навыками как во фронтенде, так и в бэкенде, а также есть у меня опыт создания мобильных приложений на React Native. В этой статье рассказываю о том, почему в Авито было принято решение развивать собственные low-code-решения и Backend-Driven UI, объясняю их устройство и делюсь достигнутыми результатами.
![](https://habrastorage.org/getpro/habr/upload_files/239/55b/0a3/23955b0a34aff30dec506634fcd26117.jpg)
Что внутри статьи:
Какую проблему мы решаем?
![](https://habrastorage.org/getpro/habr/upload_files/320/699/938/320699938c39173d77ffc7701112c5da.png)
Представим, что мы получили обратную связь от пользователей: цена в объявлении визуально сливается с заголовком. Перед нами стоит задача: поменять местами цену и заголовок, а для цены создать заметный фон. Задача требует реализации на всех платформах, где представлен Авито: Web, Android и iOS.
Как такие задачи решаются в стандартном флоу
Перед нами три платформы. Соответственно, необходимо добавить в бэклог три задачи, внести изменения в кодовую базу каждого проекта и пройти три полных релизных цикла со всеми этапами — от создания pull request и проведения code review до слияния с мастер-веткой.
В чем проблемы? Их несколько:
даже для решения самых тривиальных задач требуется привлечение квалифицированных кадров;
команда может быть загружена более приоритетными фичами;
изменения до каждой платформы доедут в разное время.
Различные платформы обновляются по-разному: для веба, например, достаточно обновить статику на сервере. А вот для мобильных приложений нужно собрать новую версию, выложить в магазин и дождаться, когда пользователи её скачают. Это отрицательно сказывается на Time to market (TTM) фичей и усложняет проверку гипотез.
Что еще можно сделать?
Вариант первый – можно сделать все на вебе.
Мы знаем, что веб обновляется быстрее всего. В мобильном приложении можно поднять специальную компоненту WebView и в ней запустить веб-контент – фактически это браузер внутри мобильного приложения.
Плюсы использования WebView:
все синхронно: не нужно ждать никаких обновлений со стороны пользователя;
кроссплатформенно: одна реализация будет работать на всех платформах;
TTM (скорость выхода фич от этапа идеи до этапа выхода в прод) сокращается, становится понятным и прогнозируемым.
Казалось бы, что еще нужно? Мобильная разработка сложнее, нужно переезжать на веб. Однако и у этого подхода есть минусы:
UI на вебе нельзя сделать полностью нативным;
проблемы с навигацией, особенно если часть приложения сделана на нативных технологиях, а часть — на WebView;
нет прямого доступа к API операционной системы. Мобильная ОС не знает, что за контент мы запускаем в WebView и не может ему давать те же привилегии, что и мобильному приложению. Ограничение можно обойти, но стабильности работе приложения это не добавит;
нельзя гибко сочетать разные технологии. Если мы используем WebView, то внутри него невозможно задействовать нативные компоненты, хотя такая необходимость может возникать.
Вариант второй — кроссплатформенные решения.
Можно посмотреть в сторону React Native от Facebook (принадлежит Meta, признанной экстремистской в РФ) и Flutter от Google. Эти решения в большей степени заточены под кроссплатформенность, что не покрывает всех кейсов, которые мы хотим решить. Про Flutter и React Native можно вообще написать отдельную статью со всеми плюсами, минусами и особенностями использования.
Какой путь выбрали мы в Авито?
Мы выбрали подход Backend-Driven UI (BDUI). Это концепция, основанная на разработке спецификации, позволяющей описывать конфигурацию пользовательского интерфейса в формате JSON. Для мобильного приложения создается движок, который получает конфигурацию от бэкенда и рендерит интерфейс с помощью нативных компонентов.
Beduin
Мы разрабатываем кроссплатформенный фреймворк Beduin.
![](https://habrastorage.org/getpro/habr/upload_files/669/863/23c/66986323c21fd019068152e752ba660c.png)
Архитектура
Beduin включает в себя универсальный движок, разработанный с помощью Kotlin Multiplatform, который отвечает за вычисление состояния приложения, а также нативные рендеры для каждой платформы, созданные с использованием соответствующих технологий и библиотек.
Движок вычисляет состояние и передает его рендеру, который отрисовывает интерфейс на основе полученных данных. Затем рендер обменивается сообщениями с движком, после чего состояние пересчитывается и UI обновляется.
Как выглядит общая механика разработки UI и доставки до пользователя?
С одной стороны, фронтенд- или мобильный разработчик описывает UI, а затем передает конфигурацию бэкенд-разработчику, который интегрирует её в код бэкенд-сервиса.
![](https://habrastorage.org/getpro/habr/upload_files/95a/80e/f51/95a80ef51e47676982a391beae26b516.png)
С другой стороны, клиентское приложение запрашивает у бэкенда beduin-сценарий, который содержит конфигурацию интерфейса.
![](https://habrastorage.org/getpro/habr/upload_files/dbe/559/9a4/dbe5599a4be04005ad5bd7f5bdec1300.png)
Бэкенд
Бэкенд не генерирует Beduin JSON «на лету». Благодаря поддержке реактивного обновления данных и декларативному подходу мы можем описать пользовательский интерфейс (UI) во всех его состояниях сразу.
Задачей бэкенда становится сбор динамических данных, представляющих собой начальное состояние приложения, и обогащение этими данными сценария. После этого сценарий отправляется обратно клиентскому приложению. Полученный JSON обрабатывается движком Beduin, предоставляя пользователю доступ к интерфейсу.
Beduin — это мощная технология, позволяющая фактически создавать UI на основе принципов SPA (Single Page Application). Мы можем собрать начальное состояние приложения и его вёрстку на сервере, а затем передать их клиентскому приложению. После этого, чтобы взаимодействовать с пользователем, нам не нужно обращаться к серверу за обновлениями вёрстки — все изменения происходят внутри клиентского приложения. К серверу мы обращаемся только для обновления данных.
Beduin в продакшн
Пример с несколькими экранами, с которыми вы можете взаимодействовать, используя Авито. Они реализованы с помощью Beduin.
![](https://habrastorage.org/getpro/habr/upload_files/aa0/6f2/f5a/aa06f2f5a2dcc78fa64ba57d41d2aece.png)
Для внесения изменений достаточно обновить конфигурацию на бэкенде — UI автоматически изменится без необходимости загрузки новой версии мобильного приложения.
Итоги
Какие плюсы от внедрения BDUI? Можно выделить следующие:
не нужно ждать, пока пользователи обновят приложение;
кроссплатформенность: один сценарий работает на всех платформах;
сценарии способны реализовывать команды, в которых может не хватать ресурсов нативной разработки;
TTM сокращается, становится понятным, появляется возможность гибко проверять гипотезы.
![](https://habrastorage.org/getpro/habr/upload_files/73a/d29/5ab/73ad295abcbd952e49a61ea0516df6ca.png)
Давайте еще раз посмотрим на общую схему:
![](https://habrastorage.org/getpro/habr/upload_files/409/8db/2fa/4098db2fa7ce1a28a0324eacac2fc100.png)
Может возникнуть закономерный вопрос: зачем нам каждый раз релизить бэкенд при изменении UI, если у нас нет необходимости генерировать JSON «на лету», а только собирать динамические данные? Не проще ли было бы прикрутить API, создать админку и управлять сценариями Beduin через неё?
Однако стоит учитывать, что Авито — это большой и сложный технологический продукт. За разные части UI могут отвечать разные микросервисы и создание отдельной админки для каждого из них приведет к значительным накладным расходам.
Bricks
Именно поэтому мы решили создать платформенное решение, которое позволило бы интегрироваться различным частям Авито. Помимо оптимизации использования BDUI, перед нами стояла задача создания инструмента, которым могли бы пользоваться не только программисты, но и контент-менеджеры. Это позволило бы эффективно решать задачи, такие как быстрая проверка гипотез и кастомизация страниц под разные категории (недвижимость, работа, авто и другие). Такой подход также способствовал бы более эффективному использованию ресурсов команд, позволяя, например, контент-менеджерам участвовать в проверке гипотез и настройке страниц для различных категорий.
![](https://habrastorage.org/getpro/habr/upload_files/792/6d1/b54/7926d1b54f4daa259ac563430325f3c1.png)
Это платформа для управления разметкой пользовательских интерфейсов с помощью независимых и переиспользуемых виджетов
В качестве клиентского рендер-движка используется Beduin
Из каких сущностей состоит Bricks?
![](https://habrastorage.org/getpro/habr/upload_files/a80/2c4/309/a802c430928ecd2cf7fe2d60b26db1f4.png)
Основной агрегирующей сущностью является шаблон. Он служит начальной точкой для определения правил описания интерфейса — будь то экран целиком или его отдельная часть.
![](https://habrastorage.org/getpro/habr/upload_files/53d/860/2b1/53d8602b1133cfea282909a98a31593c.png)
Шаблоны содержат макеты. Каждый макет включает набор виджетов, их параметры и правила взаимодействия между ними. При этом каждая версия макета сохраняется вместе со всеми настройками конфигурации.
![](https://habrastorage.org/getpro/habr/upload_files/445/eef/a4c/445eefa4c6b97cc70e1c644dd9ddc8e2.png)
Виджеты представляют собой базовые элементы управления интерфейсом. Они могут быть как простыми атомарными элементами из дизайн-системы, такими как кнопка, так и сложными частями интерфейса со своей внутренней логикой.
Как выглядит общая механика создания UI? С одной стороны, разработчики реализуют виджеты на Beduin и регистрируют их в Bricks. Таким образом, формируется каталог виджетов.
![](https://habrastorage.org/getpro/habr/upload_files/84a/67d/5b2/84a67d5b2517b24a02bd845a985e1109.png)
С другой стороны, у нас есть сотрудники, ответственные за управление интерфейсом (UI). Это могут быть как разработчики, так и специалисты без технического бэкграунда. Их задача — настраивать макеты и добавлять в них виджеты, благодаря чему в рамках шаблона создаются новые версии макетов.
![](https://habrastorage.org/getpro/habr/upload_files/bf2/7e7/5a7/bf27e75a7d2a42c3ee2784e815963cbd.png)
Как происходит регистрация виджета в Bricks?
Сперва разработчику виджета необходимо составить и описать его метаинформацию, включая название, описание и другие релевантные детали.
![](https://habrastorage.org/getpro/habr/upload_files/df2/99c/8e3/df299c8e3ff53fd342150cea14f7caf8.png)
Затем следует определить контракт — параметры виджета, которые будут управляться через Bricks. Мы следуем принципу строгой типизации, поэтому каждый параметр имеет четко определенный тип и признак опциональности.
![](https://habrastorage.org/getpro/habr/upload_files/92f/4c7/5be/92f4c75bedb83088b2e865ee09169558.png)
А дальше прямо через UI вставляется JSON с beduin-сценарием, который отвечает за отрисовку этого виджета.
![](https://habrastorage.org/getpro/habr/upload_files/f82/9f2/cf0/f829f2cf0f6b3c48bb960271fb852afa.png)
В итоге для простого виджета время от его создания до регистрации в Bricks, когда его можно будет использовать на макете, составляет около 20-30 минут.
Далее у пользователя есть возможность взять виджет из каталога, добавить его на макет, дать ему понятную метку.
![](https://habrastorage.org/getpro/habr/upload_files/2a6/b7c/0ce/2a6b7c0cee1ad2336ad4d19e271c371e.gif)
На следующем этапе открывается возможность настройки параметров виджета. Визуальный конструктор, основываясь на контракте виджета, автоматически генерирует форму, учитывая типы и обязательность полей. Пользователь может задать параметры вручную либо привязать их к динамическим данным.
![](https://habrastorage.org/getpro/habr/upload_files/291/cbc/a5c/291cbca5c8a4f1e0059e31a5649e4162.png)
Для обогащения разметки динамическими данными Bricks предоставляет две механики:
«контракт шаблона». Он определяет параметры, которые сервис-потребитель должен передать в Bricks, чтобы получить Beduin-сценарий;
«источники данных». Она представляет собой абстракцию над микросервисами Авито. Источник можно подключить к макету: тогда Bricks перед передачей разметки сервису-потребителю обращается к этим микросервисам и получает все необходимые динамические данные.
![](https://habrastorage.org/getpro/habr/upload_files/99d/82c/500/99d82c5000abdd0fc1478f0c4517e61b.png)
При настройке параметра у пользователя всегда есть выбор: ввести значение вручную или настроить динамическую привязку данных.
![](https://habrastorage.org/getpro/habr/upload_files/7d0/290/114/7d02901144aeada20c2e9476df23b6df.png)
Процесс публикации выглядит для пользователя максимально просто. Достаточно нажать кнопку «опубликовать» и отметить, что готовая версия теперь является основной. Через минуту клиентские приложения получат обновлённый интерфейс.
![](https://habrastorage.org/getpro/habr/upload_files/478/d5c/801/478d5c8012d7f92104a78fe1984a87bd.gif)
Возвращаясь к примеру про пользовательский фидбек: на связке Bricks + Beduin поменять местами название и цену, а также сделать голубой бэкграунд можно примерно за 10 минут:
Как разметка попадает к пользователю?
Платформа Bricks состоит из трех компонент. Все начинается с админ-панели, где пользователь нажимает кнопку «опубликовать».
![](https://habrastorage.org/getpro/habr/upload_files/388/a9b/a55/388a9ba55f1f9e9e74786c64e2547b2c.png)
Затем запрос поступает в компоненту Backend, ответственную за хранение каталогов виджетов, источников данных и контрактов всех сущностей. Бэкенд выполняет валидацию и направляет запрос дальше, в компоненту под названием Composition.
![](https://habrastorage.org/getpro/habr/upload_files/e44/04a/d9e/e4404ad9e1ca57ec28c05df42cb1641b.png)
Composition — высоконагруженный микросервис, отвечающий за взаимодействие с сервисами-потребителями. На этапе публикации он анализирует конфигурацию макета, собирает единый законченный сценарий из виджетов (каждый из которых является отдельным beduin-сценарием) и сохраняет в свою БД.
![](https://habrastorage.org/getpro/habr/upload_files/cd4/1f5/2ee/cd41f52eef72e79f3f60e24ed4b77a33.png)
С другой стороны, пользователь взаимодействует с клиентским приложением.
![](https://habrastorage.org/getpro/habr/upload_files/d23/dce/0fa/d23dce0fa99b317c9e99197624d2372c.png)
Клиентское приложение отправляет запрос в микросервис, ответственный за соответствующую часть UI. Микросервис собирает все нужные входные данные, согласно контракту шаблона, и отправляет запрос на получение разметки в сервис bricks-composition.
![](https://habrastorage.org/getpro/habr/upload_files/cd5/ab7/f2b/cd5ab7f2b8c89bb560db16663de8e55d.png)
Composition определяет основную версию макета, проверяет наличие подключенных источников данных и необходимость обращения к другим микросервисам. Затем он дополняет сценарий собранными данными и возвращает готовую разметку сервису-потребителю, который передает эту разметку клиентскому приложению, обеспечивая пользователю доступ к интерфейсу.
![](https://habrastorage.org/getpro/habr/upload_files/e96/dd4/66f/e96dd466fb08ffda48f2a409471c2c96.png)
Что еще можно делать в Bricks?
![](https://habrastorage.org/getpro/habr/upload_files/db2/701/2d8/db27012d82fd6461b051c724e5ef5fa4.png)
Простого создания виджетов недостаточно — важно уметь адаптировать разметку. Предположим, что нужно добавить секцию «онлайн-показ», но не для всех пользователей, а только для тех, кто соответствует определенным условиям: например, версии приложения, участию в A/B-тестировании или на основании данных, полученных от внешнего сервиса. Для решения подобных задач в Bricks реализована система зависимостей, которая позволяет гибко управлять отображением элементов интерфейса.
![](https://habrastorage.org/getpro/habr/upload_files/ccc/270/125/ccc270125da52a5e6fbc3c20126981ed.png)
Кроме того, можно настроить интерактивное взаимодействие между пользователем и интерфейсом. Например, в разделе «онлайн-показ» можно добавить кнопку, которая позволит пользователю скрыть этот блок, если он ему не нужен.
![](https://habrastorage.org/getpro/habr/upload_files/f8b/b3e/1a6/f8bb3e1a62b7bcd20fe24b5cc49c6a78.png)
Для реализации подобного взаимодействия также используются зависимости, которые можно подписать на события виджетов.
![](https://habrastorage.org/getpro/habr/upload_files/b0a/55b/a85/b0a55ba85b768905d25067d5077cc31c.png)
На этапе публикации Bricks-composition проверяет наличие зависимостей. Если зависимость не подписана на событие, она всегда будет вычисляться в момент запроса разметки и применяться к сценарию. Если зависимость привязана к событию виджета, то генерируется нужный beduin-обработчик на этапе публикации.
Что делать, если что-то пошло не так?
Bricks позволяет откатиться на любую версию макета и сделать её основной.
![](https://habrastorage.org/getpro/habr/upload_files/fdb/9f6/f2d/fdb9f6f2d81ea86512fd2031631cc820.gif)
Конечные устройства пользователей начнут получать другую версию UI в течение одной минуты.
Что мы получили в результате внедрения этих технологий:
сотрудники без навыков программирования, например, контент-менеджеры, могут настраивать и менять пользовательский интерфейс;
высвобождение ресурсов разработчиков для решения сложных и комплексных задач;
возможность быстрой проверки гипотез;
радикальное сокращение Time to market для целого ряда задач.
Спасибо за уделённое статье время! На вопросы о Bricks, Beduin и нашем опыте готов ответить в комментариях.
Этот материал написан по мотивам моего выступления на конференции Avito All Day Long. Вот его запись:
А эта ссылка — для тех, кто хочет посмотреть выступление на YouTube.
Больше о наших мероприятиях, выступлениях и том, какие задачи решают инженеры Авито, — на нашем сайте и в телеграм-канале AvitoTech. А вот здесь — всегда свежие вакансии в нашу команду.
Yevvv
Спасибо за статью. Есть ли какой-то функционал приема данных от клиентов? Если да, то как реализовано сохранение динамических полей и взаимодействие с существующими сущностями? Например я создал две формы, в каждой форме часть полей - это существующие атрибуты пользователя, другая часть - индивидуальные для каждой формы новые поля. Как будут обрабатываться сабмиты данных форм?