Backend-Driven UI — это подход для динамичного и гибкого пользовательского интерфейса, в которой бэкенд управляет не только данными в приложении, но и его вёрсткой. Сервер посредством API сообщает приложению какие компоненты и с каким контентом отображать. Сегодня многие команды разработки используют Backend Driven UI и мы в Альфе не исключение — помогает быстро выпускать фичи в продакшн. У нас в Альфе есть несколько подходов к BDUI и про один из них как раз сегодня поговорим, он называется «Виджеты». Независимость, переиспользуемость, уменьшение копипасты, стандартизация UX и обновление приложения без релизов — это всё о них.  Представим большой и нагруженный главный экран приложения, например, Альфа-Банка.  Почему он нагружен? Потому что у нас есть несколько команд: одна занимается фичами карт, другая фичами бонусов, третья — инвестиций и т.д. У каждой есть задача отрисовать и добавить на главный экран приложения свой баннер. И вот они «толпятся» на главной, толкаются, кому-то только спросить, пытаются добавить новые фичи все и сразу, возникают конфликты слияния и конфликты раскатки. Это проблема, особенно, когда команд становится всё больше (появляются новые продукты).  Для решения проблемы наши коллеги разработали систему «виджетов», когда каждая фича переехала в независимый микромодуль (виджет), которые собирались на фронте. Это позволило сильно упростить интеграцию нескольких команд. Затем появилась потребность редактирования списка виджетов, которая уже вывела систему на новый уровень, добавив возможность конфигурирования экрана на стороне сервера. Сейчас виджеты выглядят так.  Здесь есть виджеты кнопки и бонусов. Это полностью независимые модули, которые мы можем переносить на другие экраны. Например, разработчики из команды бонусов сели, написали свой виджет — красивый горизонтальный скролл с бонусами — и теперь могут добавлять его на главный экран, на экран профиля или любой другой. Это одна из главных фишек виджетов — возможность переиспользовать их на разных экранах без какой-либо доработки на фронте. Можно написать одну фичу для всех виджетных экранов и не заниматься копипастой. Виджеты идеально подходят для тех экранов, на которые интегрируются разные фичи или даже команды, как у нас: главный, профиль, платежи, витрина. В таких ситуациях, виджеты позволяют разрабатывать свою фичу не только независимо от других (а соответственно с минимум конфликтов), но ещё и добавляют возможность быстрой раскатки новой фичи (на текущих виджетах с возможностью конфигурирования), для быстрого запуска фичи в прод или для проверки гипотез. После такой небольшой вводной можно дать определение виджетам:  Виджеты — это независимые микромодули, со своим жизненным циклом, но при этом конфигурируются они извне (с помощью данных с сервера). Виджет — это полноценная фича, которую мы уместили в небольшую вьюшку и отобразили на виджетном экране вместе с другими подобными «фичами» — виджетами.  Соберём основные плюсы виджетной системы: Независимость виджетов друг от друга (а также от экрана, на котором показаны) Переиспользуемость между экранами, которая уже ничего не стоит. Написали один раз виджет, а показывать можем когда и где захотим. Уменьшение или полное отсутствие копипасты. Можем показывать сконфигурированный виджет на небольшом количестве экранов. Можем показывать один и тот же, но сконфигурированный по-разному, — проводить А/В-тесты. Параллельная загрузка информации виджетов, из чего вытекает… …Более удобный UX для пользователя: можно сразу начинать пользоваться загруженными виджетами, а не ждать, пока загрузится весь экран. Возможность релиза новых фичей без нового кода на фронте, а следовательно… …Нет необходимости проходить ревью в маркетах и ожидать раскатки новых версий. Дополнение про хвост раскатки: сейчас со сторами беда, но и раньше новую версию не все сразу ставили. А вот виджет почти всегда можно «кинуть» почти на всех клиентов и он появится везде. И многие другие, не столь значительные преимущества. Дальнейший рост и развитие системы только улучшали её, добавляя новые функции. Так, например, появилась интересная возможность виджетов — обходить фактические релизы. Система позволяет добавить новые кнопочки/вьюшки/фичи на текущие виджетные экраны, практически, в любой момент, без необходимости релизов в стор (особенно актуально в текущей ситуации). Теперь чуть подробнее как работает обычный виджетный экран, со схемами, как мы любим.  Покажем на примере схемы. (1). Пользователь заходит в приложение на виджетный экран. В это время приложение показывает анимацию загрузки и посылает запрос в Widget Management API. (2)—(4). Widget Management API получает JSON с виджетами для указанного экрана и отправляет их на фронт. (5). Виджеты показываются на экране и начинают загрузку своих данных (если необходимо). При этом с виджетами, которым дополнительная загрузка не требуется уже можно взаимодействовать. (6)—(7). Виджеты, отображающие состояние загрузки (обычно это скелетон) получают свою информацию и показывают её пользователю. Профит, можно пользоваться. Каждый виджет при желании может дополучить свои данные, при этом, например, виджет C будет показываться, а A и B ещё будут грузиться.  Но что произойдет, если нам потребуется скрыть какой-нибудь виджет (допустим, Widget C), согласно какой-нибудь бизнес-логике, например, когда данный виджет не должен быть показан у пользователя без кредитной карты. Будет странно, если на экране внезапно появится виджет кредита, он начнёт грузиться и грузиться, пока на экране не появится ошибка, ведь у пользователя кредита нет, данных нет и виджет никогда не загрузится. Будет неприятно. Или, что делать в ситуации, когда двум и более виджетам нужно получать свои данные из одного и того же сервиса (схема ниже)? Виджеты же независимы (на схеме Widget A и Widget B ходят в Service A). Widget Management API не может ходить в наши сервисы и зависеть от бизнес-логики (иначе в чём смысл, быстрота и независимость быстро потеряются). В таком случае, кажется, что преимущества системы виджетов станут её недостатками и поломают всю систему. Схема ниже показывает, что может пойти не так. Специально для решения такого рода проблем, был разработан Prefiller (префиллер) — специальный сервис, который уже умеет в бизнес-логику.  Вместе с префиллером, структура работы экрана несколько меняется.  Теперь, ответ на запрос по виджетам (1) приходит не сразу после получения JSON с виджетами для экрана, а только после заполнения этого JSON всеми нужными данными.  Сам префиллер уже ходит в нужные микросервисы, собирая все необходимые данные, заполняет JSON от Widget Management API и удаляет ненужные виджеты (если это необходимо).  На фронт уже приходят виджеты с заполненными данными (допзапрос им не требуется), а виджеты, которые не нужны пользователю, не приходят совсем.  При этом, сохраняется возможность параллельной загрузки других виджетов, не связанных с работой префиллера (на схеме виджеты D и E всё также грузят информацию из своих сервисов, а пользователь уже может взаимодействовать с экраном и фичами, которые загрузились, в том числе с помощью префиллера). Под капотом у префиллера, условно, всего две сущности — Data Collector и Data Processor. Коллектор собирает все возможные данные из Widget Management API и различных микросервисов. Процессор обрабатывает эти данные, делает определенные выводы и модифицирует итоговый JSON (например, удаляя виджет С) который затем уже уходит на фронт. Но не стоит бежать сразу переводить все экраны на префиллер и радоваться получению всех данных.  Всё-таки, параллельная загрузка на фронте позволяет обработать кейсы частичной загрузки, а также даёт пользователю почти моментальный отклик при открытии экрана (так как Widget Management API работает очень быстро). Если положить все сервисы в префиллер, то время ожидания у пользователя сильно возрастет, а так не хотелось бы. Поэтому получилось, что вместо того, чтобы использовать несколько виджетов параллельно и не ждать, пока каждый из них загрузится, мы в итоге ждём 10 секунд пока получим список виджетов (и это очень не хорошо). Для решения этой проблемы был разработан механизм Prefetch (префетч). Префетч — это реализация префиллера, но уже на фронте. Этот механизм позволяет прогреть кэши избранных виджетов перед их непосредственным показом на экране.  Так, например, на уже известной схеме, можно заметить, что Widget С загрузит свои данные (шаг 6) до непосредственного показа на экране (шаг 8), что позволит виджету отобразить свои данные сразу после показа (или не отобразить, если это заложено в его логику). Также, режим Prefetch можно включать и выключать с помощью булевой настройки для каждого виджета в Widget Management API. Сейчас для работы механизма Prefetch и Prefller нужны небольшие доработки конкретных виджетов, но, вскоре, для новых виджетов они будут доступны из коробки благодаря кодогенерации и новой модели виджета. Рассмотрим на примере виджета баннера. Там есть картинка, текст, действие. Он конфигурируется с помощью JSON. В нём самые необходимые метаданные, чтобы принять решение, что показывать на экране: название виджета, версия, тайтл, сабтайтл, ссылка для загрузки картинки и диплинк для перехода. По этим данным можно конфигурировать виджет сколько угодно раз: показывать маркетинговые баннеры, фичовые, и добавлять на все виджетные экраны без релизов и в любом порядке. Никакого Rocket Science и нет, всё довольно очевидно: 									Каждый виджет — это полноценный и самостоятельный YARCH модуль со своими интеракторами, презентерами. В упрощенном формате это VIP цикл из контроллера, интерактора и презентера, каждый из которых формирует свои данные. Интерактор получает данные (для этого помогает сущность провайдера), контроллер показывает пользователю необходимые данные с помощью view-слоя. Эта схема применима для iOS. В Android также отдельный модуль, но на другой архитектуре. Виджет сам подписывается на всевозможные обновления, на внешние ивенты (например, PTR) и обрабатывает необходимые действия: Реализовывает свой модуль данных, их получение и отображение. Включает в себя реализацию механизма Prefiller и Prefetch. Самостоятельно обрабатывает пользовательские события (тапы, свайпы и тд). Управляет собственным отображением. И всё остальное, что должен делать отдельный независимый модуль-фича. Когда пользователь нажимает на экран, именно виджет занимается тем, чтобы понять, что пользователь на него нажал, и дальнейшими обработками. Виджеты почти ничем не отличаются от полноценных фичей, но в отличие от полноценных фичей, их можно показывать на любых виджетных экранах в любом порядке — как захочется. И этот рассказ можно продолжать довольно долго, рассказывая о том, как же круто работает системы виджетов и как много бенефитов она нам даёт, но, как и в любой современной системе, в ней тоже есть проблемы. Основные проблемы текущей реализации виджетов: Сложно конфигурировать большие виджеты: у виджетов с количеством вьюшек больше одной, кратно растет размер JSON для его конфигурации. Из-за чего, иногда принимается решение вместо конфигурации — создать полностью новый виджет, а это, в свою очередь, раздувает их общее количество. Из-за проблем с проектированием сложно сразу продумать и заложить все возможности виджета и указать это в JSON для конфигурирования — приходится часто дорабатывать виджеты, добавляя в них новые рюшечки или даже новую функциональность. Из-за этого, уходит много времени на однотипные преобразования, например, у нас 3 вида кнопок и добавить в них картинку = работа x3. В результате копирования виджетов, появляется много копипасты, хотя всё должно быть наоборот. На картинке ниже несколько различных виджетов, в которые заложена одна и та же логика (получить текстовку и обработать нажатие), но при этом незначительно отличается UI. Хотя, казалось бы, должен быть один, ведь у нас есть текст, действие, иногда картинка. Но в сухом остатке это один и тот же виджет кнопки. Или, например, используются разные комбинации наших врапперов, из-за чего мы также получаем кучу «почти» одинаковых виджетов. Мы бы, конечно, хотели заранее спрогнозировать проблемы конфигурирования, но если посмотреть на виджет «Бонусы и привилегии» двух разных версий, как можно было спрогнозировать такой переезд в будущем? Тяжело. В итоге мы получили слишком большую библиотеку виджетов, которую нужно поддерживать, развивать, где-то ещё может «выстрелить». Для решения этой проблемы мы решили собраться и подумать, а что у нас получилось - в следующей статье Рекомендованные статьи: Как настраивать аналитику, если у вас Backend Driven UI Хочу в iOS-разработку: к чему готовиться на собеседовании в продуктовую команду Как снимать логи с устройств на Android и iOS: разбираемся с инструментами Багатон как в первый раз Настолки, расследования, охота на уток: как делать активности, которые запоминаются XSS атакует! Краткий обзор XSS уязвимостей Как мы ведём требования к ПО: формализация Почему мы ошибаемся при первоначальной оценке фич? Как перестать волноваться и полюбить хакатоны Также подписывайтесь на Телеграм-канал Alfa Digital — там мы постим новости, опросы, видео с митапов, краткие выжимки из статей, иногда шутим.  iOS TechLead