Как мне понадобилось написать Строитель
В мире разработки существует множество архитектурных паттернов. Часть из них мы используем каждый день, часть - реже. Наверняка каждый из вас много раз видел синглтон и фабрику. Многие их писали. А вот когда я впервые прочёл про паттерн Строитель, то не понял поначалу, в какой ситуации его можно применить. Что совсем смешно: я регулярно использовал его реализацию (QueryBuilder из фреймворка Yii), но глаз настолько замылился, что я не смог сопоставить название и функционал этого класса с соответствующим паттерном проектирования 😂 Конечно, через некоторое время до меня дошло. А еще через некоторое - нашлась ситуация, в которую паттерн Строитель вписался идеально.
На текущий момент это произошло лишь однажды. Обычно я работал на взрослых проектах, где либо подобные задачи уже были решены, либо это было страшное легаси без ресурсов на рефакторинг. Итак, где же мне понадобился Строитель?
Задача: оптимизировать товарные фиды
Проект был интернет-магазином и маркетплейсом сразу. С него выгружался добрый десяток товарных фидов. Это были файлы различных форматов со списком товаров, их категорий и характеристик. А выгружались они на другие маркетплейсы или, что чаще, на различные платформы, предоставляющие услуги рекламы и аналитики. У каждого фида были собственные настройки: какие товары/бренды/категории должны в него входить, какие данные должны быть доступны по товарам и т.п. Они хранились в БД и выставлялись в UI нашими маркетологами.
Генерация каждого фида происходила независимо от остальных. Изначально она занимала до 10 минут на каждый фид и сильно нагружала систему, т.к. очень активно читались данные из БД. И если к определенному времени надо было сгенерировать все фиды, то процедура эта растягивалась на полтора часа, в течение которых реплика БД для чтения работала на пределе.
Первая итерация оптимизации
Первым делом я сделал две вещи: Использовал для поиска товаров и получения части данных по ним SphinxSearch. Начальная версия фидов писалась еще до внедрения Сфинкса, внедрить в нее что-то новое было сложно, поэтому руки до него дошли только теперь. Сделал генерацию фидов единым процессом. Данные можно было получить один раз, и по ним - построить необходимые фиды. Фид поделился на две вещи: фильтр, который отбирал только нужные товары, и шаблон, который создавал файл из данных. Мы брали товар и пропускали через все фиды. Если фильтр пропускал его - писали информацию об этом товаре в файл. И так - каждый товар.
Такой подход вывел время создания фидов в константу: около 6 минут вне зависимости от их количества. Однако, можно было и лучше: в некоторых фидах использовалось только 20-25% товаров, но при их отдельном запуске все равно прогонялись через фильтр все товары.
Вторая итерация: Строитель
Второе, что пришло мне в голову - создать собственный QueryBuilder с верхнеуровневыми “запросами”: withBrands()
, withCategories()
, withPhotoType()
и пр. Когда требовалось сгенерировать несколько фидов - по каждому из них брался инстанс QueryBuilder’а, и они сливались в один, чтобы выбрать из БД только те товары, которые необходимы текущему набору фидов. При этом сохранилась независимость сущностей в модуле генерации фидов от структуры БД, а код непосредственно генерации изменился только в одном месте: получение товаров из репозитория изменилось с getProducts(): iterable
на getProducts(QueryBuilder …$queries): iterable
Расскажите, как часто случалось вам в вашей практике писать Строители? Расширение QueryBuilder из ActiveRecord не в счет 😉 Жду ваших комментариев в телеграм-чате блога 👍 А в новой статье увидимся через две недели!