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

в 7:00, , рубрики: Octopus, youla, youlabs, Анализ и проектирование систем, Блог компании Юла, разработка мобильных приложений, Юла

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

Когда вы ищете товары в интернете, часто возникает желание уточнить запрос, чтобы результаты поиска стали релевантнее. Будь то цвет футболки, тип коробки передач у автомобиля, количество 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, и в поле «Модель» у него появляются только те модели, которые относятся к этой марке.

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

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

В заключение

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

Автор: kdobryansky

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js