Когда вы ищете товары в интернете, часто возникает желание уточнить запрос, чтобы результаты поиска стали релевантнее. Будь то цвет футболки, тип коробки передач у автомобиля, количество USB-портов в ноутбуке или же площадь кухни в искомой квартире.
Практически с самого начала работы Юлы у нас была система плоских полей, которая обеспечивала возможность уточнения запроса. То есть в форме создания и поиска товара были доступны простые select-поля, которые позволяли сохранять товары с дополнительными параметрами, а потом искать их.
По мере развития и покорения новых вершин, Юле понадобилась новая система, которая позволила бы создавать деревья полей, с ручным вводом, выбором значений и даже получением новых полей в зависимости от ранее выбранных вариантов. И в качестве апофеоза требовалось создать простую систему управления всем этим через панель администратора.
Часть первая: «Ктулху, приди»
Начали решать эту задачу. Анализируя возможные варианты, мы постоянно сталкивались с тем, что понятия «поле», «представление» и так далее, пересекались с существующей функциональностью, что вводило собеседника в заблуждение. Избавил нас от этой путаницы коллега, который предложил радикальный способ отделения зёрен от плевел — назовём всё это «Октопус».
Почему «Октопус»? Потому что всё это похоже на осьминога. Октопус — это общая система полей, которая описывает:
- какой платформе показывать поля;
- за какой вид представления отвечает схема (создание товара, фильтр, отображение карточки, и так далее).
У осьминога есть щупальца, или тентакли (гусары, молчать!) — так мы назвали ветки дерева, в которых описано, как должны отображаться те или иные поля в форме. Это могут быть:
- поля ввода;
- поля выбора;
- группировки полей;
- текстовые поля.
Чем-то напоминает HTML-разметку, где есть теги, а у тегов есть параметры и значения, которые пользователь вводит или выбирает из заранее заданного списка.
Тех, кто был «за» и «против» названия Октопус, было примерно поровну, но спустя какое-то время после запуска системы стало очевидно, что, благодаря такому названию, все сразу однозначно понимают, о чём идёт речь.
Часть вторая: структура
Мы позволили водному существу стать названием нашей новой системы, но не всё было отдано на растерзание морским владыкам, нашлось место и для более классических обозначений. Перечислю все элементы:
- Октопус — отвечает за структуру в целом.
- Тентакль — описывает отображение каждой отдельной ветви. Клиенту передаётся поле
widget
, которое указывает, как отобразить поле. - Атрибут — хранит в себе введённое значение.
- Словарь — содержит список возможных значений для выбора из списка.
- Тэг — содержит значение, которое можно выбирать из списка.
- Параметр — атрибут или тентакль можно дополнять различными параметрами для валидации и реагирования клиентов.
- Зависимости — структура, описывающая реакцию одного поля на выбор другого.
Клиент, получивший Октопуса, может отрисовать страницу создания-редактирования товара или поиска в соответствии с описанной выше структурой. После заполнения данные сохраняются бэкендом и отправляются в поиск для индексации, что позволяет редакторам быстро создавать новые поля и сразу же использовать их в поиске.
Часть третья: представление
Здесь речь пойдет не о представлении в цирке, где октопусы хлопают тентаклями и пугают радуют публику хитросплетениями и водными процедурами (интересно, такой цирк существует?). Я расскажу о том, как наши Октопусы появляются в жизни клиента.
Как уже говорилось выше, каждая схема полей (Октопус) обладает набором параметров, которые обозначают, к какому действию относится эта схема и кому её стоит показывать. Например, Android-приложение запрашивает у бэкенда схему поисковых фильтров для раздела «Женский гардероб». Если в базе есть подходящая под заданные параметры схема, то бэкенд её возвращает, и пользователь видит поля «цвет» и «размер обуви».
Как это реализовано?
Редакторы в админке создают новую схему Октопусов, для которой указывают, что она предназначается для такого-то типа клиентского приложения (iOS, Android, web или для всех типов), что данная схема содержит поля для отображения на странице поиска, и самое главное — схема прикрепляется к определенной категории товаров.
Далее, когда пользователь в мобильном приложении заходит в поиск, клиент запрашивает у бэкенда схему полей с учетом того, что это Android, указана категория «Женский гардероб», а схема должна быть для представления «Поиск».
Часть четвертая: поиск
Пользователь в приложении ввел значения для своего объявления, клиент проверил их согласно правилам схемы и отправил на бэкенд. Тот повторно всё проверил и надежно сохранил у себя. Теперь объявление будет отображаться у покупателей с полным набором полей. Но этого мало, ведь необходимо, чтобы объявление было найдено покупателем.
Для этого мы отправляем все атрибуты, которые приходят с объявлением, в наш поиск, где данные индексируются, что позволяет моментально находить объявление по заданным параметрам. Редакторы могут в любой момент создать новые поля в админке Октопусов, а пользователи могут сразу ими пользоваться при создании объявления или поиске.
Часть пятая: интеграция
С Юлой работают b2b-партнеры, которые делятся с нами своей базой объявлений для расширения охвата. Например, если взять сотрудничество с автомобильным партнером, то там для каждого объявления во внешнем сервисе заведено огромное количество полей. Как подружить базу объявлений автомобилей с нашими Октопусами? Ответ прост — с помощью маппинга; либо, если партнер проверенный, мы можем позволить напрямую создавать поля у нас в системе.
Через Kafka мы организуем канал связи с партнером и получаем:
- обновление схемы полей для товаров партнера;
- сами товары и манипуляции с ними.
Перед началом интеграции мы узнаём, какой формат данных нам будут отправлять и какие типы полей мы получим. Заранее создав в коде условия для маппинга в наши поля, мы получаем схемы партнеров и маппим их. Можно либо создать новые поля, либо смаппить в уже существующие. После успешного маппинга мы можем получать товары и все поля будут созданы у нас в Октопусах. В будущем, если вдруг у партнеров поменяется схема, наше участие не потребуется.
Часть шестая: проблемы
При всех плюсах Октопусов, мы столкнулись и с небольшими трудностями. Если отдельные сущности кешировались в Redis, то как быть со схемами целиком? Каждый раз генерировать очень дорого, а хранить в кеше такие больше схемы проблематично. К тому же нам необходимо менять схемы, когда в дело вступают зависимые поля.
Решили разделить выдачу бэкендом схем Октопуса на два этапа:
- Получение неизменяемого дерева, которое мы кладем в локальный кэш, обновляющийся в фоновом режиме раз в n минут.
- Манипуляции с ветвями: дополнение дерева зависимостями и построение ответа без участия кеша.
Такой подход решил проблемы с кешем, и время отдачи полей свелось к минимуму.
Часть седьмая: A/B-тест
В современном продуктовом мире никуда без тестирования продуктовых фич. А/В-тесты не обошли стороной и Октопусы. Была поставлена задача: измерить заполняемость полей в определенной категории с учетом их разного количества и изменяемости значений. Благодаря гибкости схемы, реализация такого теста не потребовала много времени, и функциональность ввели в эксплуатацию в кратчайшие сроки.
Как мы это сделали?
На уровне связей Октопусов с категориями товаров мы создали проверку на попадание в эксперимент. В положительном случае отдавался другой Октопус и пользователь видел другой набор полей.
Также мы внедрили А/В-тесты и на других уровнях Октопуса: в тентакли и словари.
Часть восьмая: а где ещё применять?
В Юле Октопусы используются не только для заполнения карточек товаров и поиска по ним. Схемы Октопусов позволяют прикреплять их к любым сущностям системы, и в настоящий момент осьминоги используются в Личном кабинете пользователя и в Доставке товаров.
Часть девятая: пример
Слова словами, но без примера разобраться довольно трудно. Давайте объясню на пальцах. Возьмём структуру полей для создания товара в разделе «Недвижимость».
{
"title":"Параметры квартиры",
"widget":"group",
"order":17,
"params":{
"required":false
},
"subfields":[
{
"title":"Основные",
"widget":"section",
"order":18,
"params":{
"required":false
},
"subfields":[
{
"title":"Комнат в квартире",
"widget":"select",
"order":19,
"slug":"komnat_v_kvartire",
"type":"tag_id",
"attribute_id":1374,
"values":[
{
"id":1,
"value":"1 комната",
"order":1
},
{
"id":2,
"value":"2 комнаты",
"order":2
},
{
"id":3,
"value":"Свободная планировка",
"order":3
},
{
"id":4,
"value":"Студия",
"order":4
}
],
"params":{
"required":true
}
},
{
"title":"Этаж",
"widget":"input_int",
"order":20,
"slug":"realty_etaj",
"type":"int",
"attribute_id":1543,
"params":{
"required":true,
"min_value":1,
"max_value":500
}
}
]
},
{
"title":"Площадь",
"widget":"section",
"order":21,
"params":{
"required":false
},
"subfields":[
{
"title":"Общая площадь",
"widget":"input_float",
"order":22,
"slug":"realty_obshaya_ploshad",
"type":"float",
"attribute_id":1541,
"params":{
"required":true,
"unit":"м²",
"min_value":1,
"max_value":100000
}
}
]
},
{
"title":"Дополнительные",
"widget":"section",
"order":25,
"params":{
"required":false
},
"subfields":[
{
"title":"Высота потолка",
"widget":"input_float",
"order":25,
"slug":"building_flat_ceiling_height",
"type":"float",
"attribute_id":1518,
"params":{
"required":false,
"min_value":1,
"max_value":10,
"unit":"м"
}
}
]
}
]
}
В дереве приведён фрагмент схемы полей для подачи объявления в категории «Продажа квартиры». По этой схеме клиент может отрисовать UI для пользователя, сгруппировать поля по разным группам, провалидировать вводимые значения и отправить на бэкенд для сохранения. Рассмотрим подробнее.
Тентакли могут быть вложены друг в друга, а чтобы клиент понимал, что и как отображать, мы ввели свойство widget
, которое сообщает клиенту, что мы хотим в этом месте показать: группировку полей, текстовый блок, отступ или же полноценное поле.
Часть десятая: зависимости Октопусов
В некоторых случаях вы не можете в одной форме показать сразу все значения, которые могут оказаться в каком-нибудь select-поле. Например, если говорить об автомобилях, то размещать на одном экране форму со всеми брендами и моделями на выбор было бы крайне неудобно для пользователей, даже с учетом применения поисковых виджетов и других ухищрений.
Для решения этой проблемы мы внедрили систему зависимостей, которая позволила на бэкенде указывать, какие поля должны отображаться в тентаклях в зависимости от значений, выбранных ранее пользователем в других полях.
Пример: пользователь заходит в продажу автомобилей, выбирает марку BMW, и в поле «Модель» у него появляются только те модели, которые относятся к этой марке.
Реализовано это так:
- клиент получает схему полей;
- для тех полей, которые влияют на формирование схемы, указан специальный флаг, по которому клиент при выборе значения перезапрашивает схему и отправляет это значение бэкенду;
- пользователь выбирает поле, клиент отправляет запрос на бэкенд;
- бэкенд ищет по системе зависимостей, есть ли для указанных значений связанные поля, и если есть, то заполняет их в соответствии с заранее созданными инструкциями;
- клиент получает обновлённую схему с новым набором полей;
- теперь пользователь может выбрать значения в новых полях.
В заключение
Помимо шуток про Октопуса мы получили мощный инструмент, позволяющий быстро внедрять разные схемы полей для товаров, профилей пользователей, доставки и так далее. Администраторы через панель управления теперь могут вносить изменения и дополнять схемы, не трогая разработку и поиск. А добавление системы А/В-тестов позволило менеджерам без труда проверять эффективность разных наборов данных для ввода пользователем.
Автор: kdobryansky