Как разработать пользовательский сценарий для покупок на Яндекс Маркете: 3 способа

НачалоMVCReduxBDUI

Когда на маркетплейсе много вариантов для доставки товара, отобразить их в мобильном интерфейсе сложно и не с первого раза удаётся разработать лёгкое и удобное решение. Рустам Кенджаев, руководитель группы мобильной разработки Яндекс Маркета, расскажет, какие подходы использовали в команде маркетплейса и как пришли к самому подходящему.

С точки зрения разработки покупка и оформление заказа — это синонимы, и называются они одним словом «чекаут». В него входят разные параметры: от списка товаров и способа доставки до банковских реквизитов получателя. Чтобы отображать в интерфейсе актуальное состояние чекаута для каждого пользователя, нужно много данных от бэкенда.

Мы опирались на четыре сущности бизнес-логики:

  • Посылки
  • Опции доставки
  • Опции оплаты
  • Саммари по заказу

У нас была задача: написать код, который сможет правильно всё это отобразить. Вот какие три подхода мы использовали.

Суть подхода. Использовали базовый паттерн iOS-разработки. Это простая и понятная схема для любого iOS-разработчика, и мы посчитали её подходящей для нашей задачи.

Схема паттерна MVC

Когда-то мы в Яндексе создали сервис CAPI, основная роль которого — актуализировать состояние чекаута, которое передано от приложения. Единственный момент: когда пользователь начинал взаимодействовать с приложением, у чекаута не было состояния. Именно поэтому сервису было нечего актуализировать и передавать.

Так выглядела схема работы CAPI

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

Так выглядел процесс передачи пустого запроса на все опции и ответа по опции «Адрес»

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

Так схема первого подхода выглядела при взаимодействии пользователя с приложением

Мы немного изменили базовый паттерн MVC, потому что у нас была одна активная модель — чекаут. Внутри неё были зашиты:

  • Обращения к клиенту и бэкенду
  • Сетевые клиенты и вспомогательные зависимости — например, менеджер адресов
  • Вся бизнес-логика
Так первый подход выглядел в коде

Что пошло не так. Нам казалось, что MVC — это очень простая архитектура, которая не может сломаться. Но мы столкнулись с несколькими проблемами:

  • Модель разрослась, поэтому вносить изменения стало сложно. Мы не могли предсказать, что перестанет работать после очередных экспериментов.
  • UI стал слишком много знать о бизнес-логике, поэтому мы не могли проводить продуктовые тесты в том же интерфейсе. Например, приходилось создавать новую ячейку адреса рядом со старой, чтобы ничего не сломалось.
  • Высокий порог вхождения. Код был весь намешан в одном классе, и во всём этом стало сложно разбираться. Вместо четырёх человек в команде стало 40, фичи делали долго.

Суть подхода. Использовали архитектуру с однонаправленным потоком данных и тремя концепциями:

  • State — состояние всего приложения, описывается внутри него в отдельной структуре данных
  • Views — подписываются на состояния и актуализируют его сами на основе изменений
  • State Changes — отображает изменения состояний; состоит из Action — объектов, которые описывают изменения, и Reducer — обработчиков этих изменений

Мы взяли готовую библиотеку ReSwift с GitHub, потому что у Яндекс Маркета были похожие связи между концепциями State, Views, Action и Reducer.

Схема связей между концепциями

Дополнительно мы реализовали кастомный Action — Thunk. Он был нужен, чтобы обойти однопоточный и последовательный процесс в Redux и выполнять асинхронные операции к бэкенду.

Код для Thunk

Чекаут изменился и распался на две сущности:

  1. State — наша базовая сущность из бизнес-логики.
  2. UserInput — структура, в которой сохранялись данные, введённые или изменённые пользователем.

Action превратились в enum, в котором каждый кейс содержит смысловую нагрузку в зависимости от того, что пользователь выбрал и куда он кликнул.

Примеры кода для State и enum

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

У нас в подобных редьюсерах был сброс пользовательского ввода. Там же находилась логика по приоритизации предвыбранной опции оплаты.

После реализации Redux-подхода у нас появилась возможность писать тесты на бизнес-логику, потому что UI оказался от неё отделён. Также мы сепарировали куски бизнес-логики друг от друга, потому что появилось много чистых функций, которые проще тестировать и в которые разработчикам проще вникать. Это помогло решить проблему с командой и понизило порог входа в код, что ускорило запуск фич.

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

Чтобы это сделать, на старте мы добавили ещё один запрос к бэкенду, после которого получали массив альтернатив и актуализировали данные. В результате работа усложнилась и замедлилась.

Так выглядела новая схема для Redux

Всю эту сложную реализацию мы раскатали на iOS, Android и web, но не были уверены, что на всех платформах сценарии будут работать одинаково хорошо.

Суть подхода. Внедрили API, через который нам с сервера приходит вёрстка, готовая для отображения в интерфейсе. Благодаря BDUI мы теперь можем добавлять фичи без дополнительного ревью со стороны App Store и других сторов.

Мы начали реализовывать этот подход полтора года назад и создали свой движок Flex. Он умеет отображать контент на UIkit, DivKit, Compose, SwiftUI. Это даёт больше возможностей для работы на разных платформах, при этом мы тратим меньше сил и времени на адаптацию.

Так выглядит схема работы для подхода BDUI

Чтобы внедрить BDUI в Яндекс Маркете, нужно было полностью переделать бэкенд. Мы перенесли на него состояние чекаута из приложения для хранения данных и интеграции с другими микросервисами. Так мы уменьшили количество запросов чекаута с трёх до одного.

Вот как упростилась схема:

  • На старте — проверяем /getState и получаем готовый UI для отрисовки
  • При изменении данных — отправляем запрос /patchState, в котором передаётся тип изменений и параметр полезной нагрузки payload
Схема проверки /getState и /patchState

Подход BDUI у нас состоит из трёх основных компонентов:

  1. DivKit — вёрстка на переменных, чтобы быстрее выбирать интерактивные элементы.
  2. Actions — взаимодействия пользователей.
  3. MAPI — мобильный бэкенд для BDUI, в котором мы подготавливаем вёрстку.

BDUI хорошо работает для экранов со статической информацией. Чтобы получить быстрый отклик при прямом взаимодействии с пользователем, мы добавили несколько лайфхаков для чекаута. Например, фейковый документ на время загрузки. Если пользователь выбирает экран адресов, мы показываем скелетон по дополнительному запросу, пока страница загружается. Это уменьшает время ожидания и приближает интерфейс к нативному.

В результате удалось ускорить открытие чекаута с 5 300 мс до 1 600 мс, а ещё работать одновременно на iOS и Android и писать фичи без отдельных релизов.

Что можно улучшить. Несмотря на то что новая архитектура нас устраивает, есть мелочи, которые можно улучшить. У нас их две:

  1. При релизах бэкенда невозможно провести регресс всей функциональности, поэтому бывает, что релизы приходится откатывать из-за внесённых ошибок.
  2. Ломается обратная совместимость в бэкендах вёрстки и актуализатора данных из-за несинхронного релизного цикла — без версий приложения.

Поделитесь увиденным

Скопировать ссылку
ТелеграмВКонтакте