company_banner

Повесть об Октопусе



    Когда вы ищете товары в интернете, часто возникает желание уточнить запрос, чтобы результаты поиска стали релевантнее. Будь то цвет футболки, тип коробки передач у автомобиля, количество USB-портов в ноутбуке или же площадь кухни в искомой квартире.

    Практически с самого начала работы Юлы у нас была система плоских полей, которая обеспечивала возможность уточнения запроса. То есть в форме создания и поиска товара были доступны простые select-поля, которые позволяли сохранять товары с дополнительными параметрами, а потом искать их.

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

    Часть первая: «Ктулху, приди»


    Начали решать эту задачу. Анализируя возможные варианты, мы постоянно сталкивались с тем, что понятия «поле», «представление» и так далее, пересекались с существующей функциональностью, что вводило собеседника в заблуждение. Избавил нас от этой путаницы коллега, который предложил радикальный способ отделения зёрен от плевел — назовём всё это «Октопус».

    Почему «Октопус»? Потому что всё это похоже на осьминога. Октопус — это общая система полей, которая описывает:

    • какой платформе показывать поля;
    • за какой вид представления отвечает схема (создание товара, фильтр, отображение карточки, и так далее).

    У осьминога есть щупальца, или тентакли (гусары, молчать!) — так мы назвали ветки дерева, в которых описано, как должны отображаться те или иные поля в форме. Это могут быть:

    • поля ввода;
    • поля выбора;
    • группировки полей;
    • текстовые поля.

    Чем-то напоминает HTML-разметку, где есть теги, а у тегов есть параметры и значения, которые пользователь вводит или выбирает из заранее заданного списка.

    Тех, кто был «за» и «против» названия Октопус, было примерно поровну, но спустя какое-то время после запуска системы стало очевидно, что, благодаря такому названию, все сразу однозначно понимают, о чём идёт речь.

    Часть вторая: структура


    Мы позволили водному существу стать названием нашей новой системы, но не всё было отдано на растерзание морским владыкам, нашлось место и для более классических обозначений. Перечислю все элементы:

    • Октопус — отвечает за структуру в целом.
    • Тентакль — описывает отображение каждой отдельной ветви. Клиенту передаётся поле widget, которое указывает, как отобразить поле.
    • Атрибут — хранит в себе введённое значение.
    • Словарь — содержит список возможных значений для выбора из списка.
    • Тэг — содержит значение, которое можно выбирать из списка.
    • Параметр — атрибут или тентакль можно дополнять различными параметрами для валидации и реагирования клиентов.
    • Зависимости — структура, описывающая реакцию одного поля на выбор другого.

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

    Часть третья: представление


    Здесь речь пойдет не о представлении в цирке, где октопусы хлопают тентаклями и пугают радуют публику хитросплетениями и водными процедурами (интересно, такой цирк существует?). Я расскажу о том, как наши Октопусы появляются в жизни клиента.

    Как уже говорилось выше, каждая схема полей (Октопус) обладает набором параметров, которые обозначают, к какому действию относится эта схема и кому её стоит показывать. Например, Android-приложение запрашивает у бэкенда схему поисковых фильтров для раздела «Женский гардероб». Если в базе есть подходящая под заданные параметры схема, то бэкенд её возвращает, и пользователь видит поля «цвет» и «размер обуви».

    Как это реализовано?

    Редакторы в админке создают новую схему Октопусов, для которой указывают, что она предназначается для такого-то типа клиентского приложения (iOS, Android, web или для всех типов), что данная схема содержит поля для отображения на странице поиска, и самое главное — схема прикрепляется к определенной категории товаров.

    Далее, когда пользователь в мобильном приложении заходит в поиск, клиент запрашивает у бэкенда схему полей с учетом того, что это Android, указана категория «Женский гардероб», а схема должна быть для представления «Поиск».

    Часть четвертая: поиск


    Пользователь в приложении ввел значения для своего объявления, клиент проверил их согласно правилам схемы и отправил на бэкенд. Тот повторно всё проверил и надежно сохранил у себя. Теперь объявление будет отображаться у покупателей с полным набором полей. Но этого мало, ведь необходимо, чтобы объявление было найдено покупателем.

    Для этого мы отправляем все атрибуты, которые приходят с объявлением, в наш поиск, где данные индексируются, что позволяет моментально находить объявление по заданным параметрам. Редакторы могут в любой момент создать новые поля в админке Октопусов, а пользователи могут сразу ими пользоваться при создании объявления или поиске.

    Часть пятая: интеграция


    С Юлой работают b2b-партнеры, которые делятся с нами своей базой объявлений для расширения охвата. Например, если взять сотрудничество с автомобильным партнером, то там для каждого объявления во внешнем сервисе заведено огромное количество полей. Как подружить базу объявлений автомобилей с нашими Октопусами? Ответ прост — с помощью маппинга; либо, если партнер проверенный, мы можем позволить напрямую создавать поля у нас в системе.

    Через Kafka мы организуем канал связи с партнером и получаем:

    • обновление схемы полей для товаров партнера;
    • сами товары и манипуляции с ними.

    Перед началом интеграции мы узнаём, какой формат данных нам будут отправлять и какие типы полей мы получим. Заранее создав в коде условия для маппинга в наши поля, мы получаем схемы партнеров и маппим их. Можно либо создать новые поля, либо смаппить в уже существующие. После успешного маппинга мы можем получать товары и все поля будут созданы у нас в Октопусах. В будущем, если вдруг у партнеров поменяется схема, наше участие не потребуется.

    Часть шестая: проблемы


    При всех плюсах Октопусов, мы столкнулись и с небольшими трудностями. Если отдельные сущности кешировались в Redis, то как быть со схемами целиком? Каждый раз генерировать очень дорого, а хранить в кеше такие больше схемы проблематично. К тому же нам необходимо менять схемы, когда в дело вступают зависимые поля.

    Решили разделить выдачу бэкендом схем Октопуса на два этапа:

    • Получение неизменяемого дерева, которое мы кладем в локальный кэш, обновляющийся в фоновом режиме раз в n минут.
    • Манипуляции с ветвями: дополнение дерева зависимостями и построение ответа без участия кеша.

    Такой подход решил проблемы с кешем, и время отдачи полей свелось к минимуму.

    Часть седьмая: A/B-тест


    В современном продуктовом мире никуда без тестирования продуктовых фич. А/В-тесты не обошли стороной и Октопусы. Была поставлена задача: измерить заполняемость полей в определенной категории с учетом их разного количества и изменяемости значений. Благодаря гибкости схемы, реализация такого теста не потребовала много времени, и функциональность ввели в эксплуатацию в кратчайшие сроки.

    Как мы это сделали?

    На уровне связей Октопусов с категориями товаров мы создали проверку на попадание в эксперимент. В положительном случае отдавался другой Октопус и пользователь видел другой набор полей.

    Также мы внедрили А/В-тесты и на других уровнях Октопуса: в тентакли и словари.

    Часть восьмая: а где ещё применять?


    В Юле Октопусы используются не только для заполнения карточек товаров и поиска по ним. Схемы Октопусов позволяют прикреплять их к любым сущностям системы, и в настоящий момент осьминоги используются в Личном кабинете пользователя и в Доставке товаров.

    Часть девятая: пример


    Слова словами, но без примера разобраться довольно трудно. Давайте объясню на пальцах. Возьмём структуру полей для создания товара в разделе «Недвижимость».

    Пример JSON из категории Продажа квартиры — Параметры квартиры
    {
       "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, и в поле «Модель» у него появляются только те модели, которые относятся к этой марке.

    Реализовано это так:

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


    В заключение


    Помимо шуток про Октопуса мы получили мощный инструмент, позволяющий быстро внедрять разные схемы полей для товаров, профилей пользователей, доставки и так далее. Администраторы через панель управления теперь могут вносить изменения и дополнять схемы, не трогая разработку и поиск. А добавление системы А/В-тестов позволило менеджерам без труда проверять эффективность разных наборов данных для ввода пользователем.
    Юла
    Сlassified 2.0

    Похожие публикации

    Комментарии 0

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

    Самое читаемое