Всем привет! Меня зовут Артем Пескишев, я iOS-разработчик в Авито. Хочу рассказать, как мы завели управляемый с бэкенда сценарий публикации новых объявлений в наших мобильных приложениях.  Разберём, что такое пошаговые сценарии в приложениях и какие проблемы со сценарием размещения объявления были у нас. Статья — про разработанный нами инструмент для решения этих проблем и результаты его внедрения.   Backend-driven UI — это подход к проектированию интерфейсов, при котором в приложении есть набор компонентов, эдаких строительных блоков, с которыми приложение умеет работать, а расположение, порядок и наполнение этих блоков приходят с бэкенда.  Backend-driven UI решает проблему хардкода большого количества полей в формах на клиенте, а также связей между этими полями. Если вся логика вынесена на бэкенд, то клиент становится более гибким и расширяемым с бэкенда. Это значит, что не надо постоянно релизить новые версии приложений. Часть валидации полей также можно перенести на клиент и обновлять правила валидации с бэкенда. При хардкоде же приходится всегда лезть в сеть, чтобы проверить отдельные поля. В статье я буду рассказывать про backend-driven сценарий приложения — это более широкий взгляд на backend-driven UI. Пошаговый сценарий — это порядок действий, которые пользователь должен совершить для получения необходимого результата. Классический пример — это настройка нового айфона. После включения телефона пользователь последовательно заполняет определённые параметры: страну, сеть Wi-Fi, место хранения данных и свой Apple ID. В итоге айфон полностью настроен.  Онбординг в приложении — тоже пошаговый сценарий. Например, перед тем как начать пользоваться MyFitnessPal, нужно заполнить несколько параметров. На их основе приложение генерирует норму калорий, которую нужно потреблять в день.  В приложениях Авито есть два основных сценарий размещения объявления. Первый характерен для категории «Авто». Пользователь выбирает основную категорию объявления и уточняет её. Например, он хочет продать автомобиль с пробегом. Далее заполняет все параметры, которые относятся к категории, после чего переходит на экран контактов, добавляет информацию о себе и нажимает кнопку «разместить объявление». После этих действий объявление попадает на сайт.   Второй сценарий характерен для подачи объявления в «Личные вещи». Пользователь выбирает эту категорию, фотографирует товар, указывает его название. На основе заполненных параметров Авито предсказывает, в какую подкатегорию разместить объявление. Далее пользователь заполняет описание и цену товара, переходит на экран контактов и нажимает «разместить объявление». Оба основных сценария у нас были жёстко зашиты на клиенте. Для их изменения или расширения функциональности нам приходилось исправлять клиент и выпускать новую версию приложения. В итоге от идеи до того, как доработки попадут к пользователям, проходило много времени. Например, пользователи часто ошибались в выборе подкатегории в услугах. Нам требовалось вынести название объявления на первый экран при размещении, чтобы подключить предсказание подкатегорий по названию. Вся функциональность уже была готова, нужно было просто добавить в сценарий два экрана и убрать одно поле. На это ушло восемь дней от момента, когда разработчик начал делать задачу до её закрытия QA-инженером. Это много.  Мы хотели улучшать сценарий размещения объявления, чтобы пользователи могли опубликовать максимально качественное объявление в максимально короткий срок. Чтобы понять, какой сценарий является оптимальным, нам требовалось проводить быстрые эксперименты. Мы хотели при необходимости быстро изменять набор параметров на экране и их порядок, а также менять экраны между собой местами. Чтобы это работало, мы сделали свой инструмент.  Мы хотели, чтобы из админки приходил набор параметров, которые должны отображаться на форме, а также массив шагов, которые будут составлять сценарий размещения объявления. Бэкенд получал бы эти данные и обогащал их с помощью микросервисов. Он дополнительно подставлял бы определённые значения или убирал какие-то поля в зависимости от профиля пользователя. После чего массив обогащённых параметров и шагов попадал бы в приложение. Эта архитектура актуальна для iOS, Android, а также для Desktop и мобильной версии сайта Авито.  После того, как приложение получило данные о параметрах и шагах, оно могло бы выстроить стек шагов для перемещения пользователя. То есть создать сценарий публикации объявления. Для реализации идеи нужно было: Наша большая форма подачи объявления строилась из многих «кубиков». На клиенте поддерживались различные типы компонентов:  Часть параметров мы получали с сервера. Это были уникальные для конкретных категорий параметры, например, год выпуска для объявлений с продажей автомобиля или этаж для квартиры. Другая часть параметров, которые были абсолютно во всех категориях объявлений, были захардкожены. Это название, описание, цена и фотографии.  Чтобы встроить их в систему «конструктора» формы подачи объявления, мы перенесли всю логику на бэкенд. После этого форма подачи объявления в рамках одного экрана стала строиться динамически.  Дальше было необходимо понять концепцию шагов в сценарии подачи объявления. Шаги — это новые блоки конструктора, из которых мы можем собирать различные сценарии размещения объявления.  Мы решили, что шаг — это не просто один экран, а некая часть сценария. Он может содержать в себе несколько экранов или не содержать экранов вообще. Шаг может включать заголовок, подзаголовок, определённый тип и набор параметров, которые отображаются на экране. Но также может быть шаг, во время которого мы отправляем запрос на сервер, например для валидации заполненных пользователем данных. Никакого специфического интерфейса пользователь при этом не видит — на экране просто вращается активити-индикатор. Основной тип шагов — это шаг заполнения параметров. Он может выглядеть по-разному в зависимости от набора полей. Это могут быть просто три поля типа select. Или одно поле с добавлением фотографий с подзаголовком у экрана. Это может быть шаг выбора места встречи с покупателем, где под полем есть мотивирующий заполнить его текст. Но это всё один и тот же тип шага, просто его внешний вид зависит от набора параметров, которые он отображает. Второй основной тип — это шаг выбора категории объявления. Он включает в себя несколько экранов, потому что пользователю необходимо дойти до конечной подкатегории.  Ещё один из сформулированных шагов — выбор значения. Иногда мы хотим, чтобы пользователь просто выбрал быстро одно значение из многих. И вместо того, чтобы заставлять его совершать лишние действия, мы превратили отдельный шаг в шаг выбора значения. В нём нужно сделать всего один клик. Вот как это было устроено раньше. Представим, что пользователь публикует объявление опродаже недвижимости. Открывается экран с одной ячейкой параметра «Вид объекта», где значение не заполнено. Пользователь кликает на параметр, и открывается экран с вариантами. Пользователь выбирает один из них, возвращается на первый экран с ячейкой и нажимает кнопку «Продолжить». Мы изменили шаг, и теперь на экране «Вид объекта» сразу показываются ячейки со значениями. Пользователь выбирает нужное и сразу переходит дальше. То есть нужно сделать один клик вместо трёх. Следующий экран уникален для подачи объявления в категорию «Авто». Это шаг заполнения VIN, где пользователь может вбить VIN машины или отсканировать свидетельство о регистрации транспортного средства. По этим данным мы сами заполняем до 18 параметров автомобиля. Также есть шаг подсказки подкатегории, где мы на основе заполненных пользователем данных, например, заголовка и описания, предсказываем, в какой подкатегории стоит разместить объявление. Так как некоторые пользователи сталкиваются с проблемами выбора категории из-за того, что их достаточно много, мы упрощаем им жизнь за счёт таких подсказок. Если предложенные варианты пользователю не нравятся, он всегда может выбрать подкатегорию самостоятельно. После того как мы сформировали визуальные типы шагов, у нас остались вопросы: Чтобы решить эти вопросы, мы сформировали так называемые технические шаги. Во время выполнения технического шага пользователь не видит новые экраны, просто на текущем крутится активити-индикатор. В этот момент приложение отправляет запрос к серверу и ждёт ответа. Технических шагов получилось три: После того, как мы сформировали типы шагов и сделали backend-driven UI в рамках каждого, оставалось интегрировать всё это в единую архитектуру. В цепочке интеграции сценария размещения объявления три участника: админка, бэкенд и приложение или сайт.  Админка — это визуальный UI-интерфейс для проектирования сценариев. Любой специалист, который умеет с ней работать, может создать шаги сценария размещения объявления. Все созданные шаги превращаются в джисонку в виде массива. Каждый шаг индивидуально настраивается: в админке задаётся его заголовок, подзаголовок, параметры и тип. После этого для каждого шага забиваются параметры. Они составляют отдельную джисонку с массивом всех параметров, которые участвуют в сценарии размещения объявления. В итоге админка возвращает собранную третью финальную джисонку из параметров и шагов, и передаёт новую структуру в бэкенд. Бэкенд получает эту структуру и обогащает её, используя различные микросервисы. Например, в форме размещения объявления есть поле «Адрес», который пользователь должен указать. Но обычно мы знаем, где пользователь размещает свои объявления, и можем предзаполнить поле. Бэкенд идёт в микросервис пользователя, получает его стандартный адрес, и подставляет значение в поле "value" параметра адреса. После чего обогащённая структура попадает в клиенты — iOS, Android или сайт. Расскажу про архитектуру нашего фреймворка.   Координирующий модуль DynamicPublish загружает первоначальные данные, инициирует открытие каждого шага, обрабатывает навигацию между экранами и закрытие сценария подачи, если пользователь решил прервать сценарий или подача завершена. Первое, что делает координирующий модуль динамической подачи — загружает шаги. Он лезет в DataService, который получает данные с бэкенда, маппит их в объекты типа параметр-дата и стек-дата, и возвращает их. После чего модуль берёт первый шаг сценария и инициирует его открытие через обработчик этого шага. Появляется новая сущность — StepHandler. Она открывает один из модулей, обрабатывает результат его выполнения и обновляет данные в стейте, который работает с данными сценария размещения объявления. Для каждого типа шага существует отдельный StepHandler, который открывает тот или иной модуль. Например, есть шаг типа Wizard — он вызывает StepHandler, а тот инициирует модуль с выбором подкатегорий объявления. А тип шага Steps просто показывает активити-индикатор. Экран типа Select может открыть модуль выбора значения. И шаг Params показывает экран со списком параметров. У каждого модуля несколько возможных результатов выполнения. Во-первых, пользователь может закрыть подачу и прервать сценарий размещения объявления. Он может уйти назад с текущего экрана и вернуться на предыдущий. Или после того, как заполнил все параметры, нажать кнопку «Продолжить» и перейти к следующему экрану. Модуль возвращает результат выполнения в StepHandler. StepHandler обновляет данные в дата-холдере теми параметрами, которые пользователь заполнил в форме. И возвращает полученный результат в координирующий модуль. Координирующий модуль может либо показать следующий шаг, если пользователь нажал «Продолжить», либо закрыть подачу или закончить её в зависимости от того, разместил ли пользователь объявление. Модуль также может обработать шаг назад, если пользователь нажал кнопку «Назад». Суммируем. Модуль динамической подачи создаёт каждый раз новый StepHandler для каждого модуля. Пользователь совершает какие-то операции — выбирает категорию, заполняёт поля, и результат возвращается в координирующий модуль. Это продолжается доn тех пор, пока пользователь не дойдет до последнего шага — публикации объявления, либо не прервет сценарий публикации. Сравним ситуации до внедрения нового инструмента и после. Например, была задача разделить экран названия объявления и цены на два раздельных, чтобы пользователь сначала заполнял название, а потом выбрал цену. На это у нас уходило шесть дней. Из-за хардкода приходилось тратить кучу времени на переделку, хотя задача кажется достаточно простой. С внедрением нового инструмента стало значительно проще. У нас есть форма подачи объявления в приложении и есть форма в админке. Мы редактируем первый шаг — удаляем цену, сохраняем, и добавляем отдельный шаг для цены товара. Вводим название нового шага, выбираем поле, которое должно на нём отображаться, конфигурируем шаг, выставив ему корректный тип, и сохраняем. После чего перезагружаем форму и получаем новый сценарий. Обновлять приложение нет необходимости.  В итоге нужно потратить всего 42 секунды на то, чтобы внести изменения в сценарий размещения объявления. По сравнению с шестью днями это фантастика.  Плюс — работать с админкой может любой. Сейчас конфигурацией всех шагов занимаются не инженеры, а продакты. Это люди, которые наиболее заинтересованы в проведении быстрых экспериментов, чтобы найти оптимальный сценарий размещения объявления. Бизнес получил возможность проводить быстрые эксперименты с настройкой сценария подачи объявления без обновления версий приложений. Можно запускать множество А/В-тестов параллельно и получать оперативную обратную связь от пользователей.  Пользователи получили удобный интерфейс публикации объявления. Эксперименты позволяют нам понять, какие сценарии наиболее оптимальны и удобны по скорости подачи и качеству размещения объявления. Разработчики получили гибкую и легко расширяемую систему для внесения изменений. Теперь не нужно тратить время на перемещение шагов между собой внутри приложения и изменение порядка полей. Всё, что должны делать разработчики — это расширять существующую систему. Например, добавлять новые типы шагов, если это необходимо, или поддерживать новые типы полей. На разработку инструмента ушло полгода. Мы не сразу строили космолёт, а делали его кусочками. Сначала поддержали все шаги для подачи объявлений по продаже транспорта — это заняло два месяца работы команды из трёх человек. Когда мы поняли, что MVP работает, начали расширять его на остальные категории. Сейчас сценарии во всех категориях объявлений конфигурируются через админку, баги исправлены, и всё работает.