Как стать автором
Обновить

Фасетные фильтры: структура и взаимодействие компонент

Время на прочтение6 мин
Количество просмотров2K

В предыдущей статье я описал как построить запрос к Эластику для полнотекстового и фасетного поиска. Здесь расскажу как организовать структуру и взаимодействие компонент, чтобы построить такой запрос.  

Мы используем vue, vuex, vue-route, поэтому дальше повествование будет в этом контексте. В основе взаимодействия лежат хуки компонент, хранилище состояния, роутер. Такую структуру можно воспроизвести на любом фреймворке с такими же составными частями.

UI и UX фасетного поиска   

Что бы что-то найти пользователь может:

  1. сделать поиск по тексту;

  2. для нескольких характеристик выбрать одно или нескольких значений;

  3. для некоторых характеристики установить диапазон значений;

  4. сортировать результат;

  5. просмотреть следующую страницу результата. Здесь есть варианты: подгрузить результат или посмотреть на отдельной странице;

  6. перейти по ссылке, в которой уже установлены все параметры поиска.

Компоненты UI взаимодействуют по правилам: 

  1. Изменения значений компоненты приводит к изменению соответствующего параметра в URL. И наоборот, если пользователь изменяет значения параметра URL - меняется значение компоненты, ну… или не меняется, если пользователь ввел что-то непонятное.

  2. Новое значение в поле полнотекстового поиска сбрасывает значения остальных компонент.

  3. Новое значение для характеристики сбрасывает сортировку и пагинацию.

  4. Новое значения сортировки сбрасывает пагинацию.

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

Ситуация  выглядит так, что компоненты уникальны и связаны уникальными правилами, и выстроить универсальную структуру не получиться. Но мы попробуем.

Структура  

UI компоненты и миксины

  1. ProductGrid - отображает результат и управляет пагинацией. В планах исправить, потому что это  нарушение Single Responsibility. Грид существует в единственном экземпляре.

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

Методы-хуки:

  1. initFilterAndBuildFetchCriteria (route: RouteLocation, currentFetchCriteria: FetchCriteriaInterface): FetchCriteriaInterface - хук инициализации , инициализирует компонент значениями из роута. Если необходимо изменяет критерии и возвращает их как результат. 

  2. buildFetchCriteriaFromInternalValue (currentFetchCriteria: FetchCriteriaInterface): FetchCriteriaInterface - устанавливает новые критерии и возвращает их как результат  

  3. buildRouteLocation (currentFetchCriteria: FetchCriteriaInterface, currentRoute: RouteLocation): RouteLocation - формирует часть объекта роута на основе критериев 

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

Сущности бизнес логики

ProductSearchPage  - поставщик результатов поиска, в нашей реализации это модуль Vuex. Реализует логику взаимодействия всех компонент. Содержит:

  1. Список компонент, которые формируют критерий.

  2. Критерии поиска - неизменяемая сущность, предоставляет интерфейс для создания нового экземпляра на основе текущего.

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

  4. Список объектов для отображения. Если пагинация постраничная, то список объектов совпадает со списком текущего результата.  В случае подгрузки, список содержит объекты от первой страницы до текущей.

Сервисы инфраструктуры

  1. ProductService - клиент к АПИ поиска, в нашем случае Эластика.

  2. QueryBuilder - создает запрос к Эластику на основе критериев по алгоритму из предыдущей статьи.

Алгоритм взаимодействия 

Инициализация 

Инициализация происходит при переходе на страницу поиска или при обновлении страницы. Компоненты создаются и монтируются в DOM. Компоненты, которые могут изменять критерии, регистрируют себя в ProductSearchPage. При регистрации ProductSearchPage добавляет компонент во внутренний репозиторий, другие операции не выполняются.

Затем Vue запускает наблюдатели (watch) в компонентах. ProductGrid наблюдает за роутом. Vue считает, что роут изменен при загрузке страницы или программном изменении, поэтому запускается наблюдатель. Наблюдатель грида обращается к ProductSearchPage, передает ему новый роут для запуска поиска. Важные детали:

  • наблюдатель - единственная точка кода, которая запускает поиск, 

  • новый роут формирует критерии для поиска с нуля. Роут - единственный источник правды, потому что может быть изменен пользователем руками. Мы не можем контролировать этот процесс

Поиск

ProductSearchPage вызывает хук initFilterAndBuildFetchCriteria последовательно для всех зарегистрированных компонент. Он обязан быть у каждого компонента. Хук:

  1. инициализирует значение компонента значением из $route, если оно есть; 

  2. изменяет критерии: 

    1. если в роуте есть значение, оно добавляется  в критерии;

    2. если в роуте нет значения, а в критериях есть, то удаляется из критериев.  

Когда инициализация закончена, каждый компонент имеет инициирован значениями из роута, и критерии поиска сформированы.

ProductSearchPage выполняет поиск через сервис ProdcutService. ProdcutService 

  1. получает критерии поиска, создает из них запрос через QueryBuilder;

  2. отправляет запрос в АПИ;

  3. возвращает объект результата.

ProductSearchPage преобразует сырой результат к нужному формату и заменяет текущий результат новым. Далее обновляет список объектов для отображения: если пагинация постраничная - заменяет, если подгрузка - добавляет. ProductGrid наблюдает за списком для отображения и обновляет UI. На этом поиск закончен. 

Применение критерия поиска

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

ProductSearchPage принимает компонент и вызывает в нем хук buildFetchCriteriaFromInternalValue. Хук изменяет критерии в соответствии с нужной стратегией и возвращает новый экземпляр.

ProductSearchPage получает новые критерии и запускает построение нового роута. Для этого в каждом зарегистрированном компоненте вызывается хук buildRouteLocation, новые критерии передаются как параметр. Хук проверяет наличие в критериях своего параметра, если он есть -  формирует часть роута.  ProductSearchPage объединяет все части роута в один объект,  сохраняет новые критерии и обновляет роут. За изменением роута следит ProductGrid, который запускает поиск, как описано выше. 

Чуть выше я писал “новый роут формирует критерии для поиска с нуля”. Возникает правильный вопрос: зачем строить критерий, почему не строить сразу роут?

Причина проста - интерфейс критериев поиска позволяет реализовывать стратегии взаимодействия компонент: что-то сбросить, что-то оставить. Роут не имеет таких методов, структура роута  определена библиотекой  vue-route. Поэтому мы сначала изменяем критерии, а потом на их основе формируем роут. 

Деструктуризация 

ProductGrid уничтожается, когда  пользователь покидает страницу поиска. Деструктор компонента приводит  ProductSearchPage в начальное состояние: нет результата, нет критериев и т.п. При повторном входе на страницу  ProductSearchPage будет инициализирован из роута по новой.

Динамическое подключение фильтров 

У нас нет фильтров привязанных к категориям. Таковы бизнес требования. Но  ничего не мешает их реализовать. Наши категории отображаются в URL path. При выборе категории в фильтре будет сформирован новый роут с новым path. Применение нового роута приведет к перезагрузке страницы, запуститься инициализация. Можно до инициализации получить список характеристик, по которым нужна фильтрация, и создать нужные компоненты. Далее по схеме описанной выше. Ну… это в теории =)     

Возможные улучшения

В текущей реализации много чего можно улучшить:

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

  2. Вынести обработку отображения страниц в виде пагинации или подгрузки  в отдельную стратегию.

  3. Избавить ProductGrid от управление пагинацией. 

  4. Оптимизировать структуру ProductSearchPage, потому что список  объектов для отображения и текущий результат  пересекаются по объектам.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Нужно ли вставлять UML диаграммы в подобные статьи?
33.33% не знаю ничего про UML2
0% не смотрю диаграммы, для меня текст понятнее, если нормально написано0
33.33% диаграмма классов всегда полезна, остальное лучше на словах2
66.67% без диаграмм сложно, обязательно нужно диаграмму классов и деятельности4
Проголосовали 6 пользователей. Воздержались 2 пользователя.
Теги:
Хабы:
Рейтинг0
Комментарии0

Публикации

Истории

Работа

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область