Давайте пофильтруем данные с помощью микросервиса
Драма по поводу хорош или плох mSearch2 не утихает. Многие пользователи системы видят ее исключительно в монолитом виде что очень сильно ограничивает кейс системы. Причем любой.
Давайте представим что у вас есть интернет-магазин на MODX + miniShop2 с очень большим кол-вом товаров и каким-никаким хайлоадом :)
Использование встроенных фильтров в mSearch вас немного ограничивает. Как минимум тем, что не кеширует результат.
Предлагаю припилить к нашему любимому MODX небольшой микросервис на Laravel который будет фильтровать данные по входному запросу, а также отдавать нам список айдишников которые подходят под выборку.
Предполагается что вы знаете что такое composer, как его установить и в целом умеете пользоваться базовым PHP
Данная команда создаст вам новый Laravel проект в папке laravel_filters.
В Laravel есть единый файл конфигурации в котором можно указать подключения к различным сервисам.
Чисто для примера мы будем использовать ту же самую базу что использует MODX. Однако, на практике я бы предпочитал чтобы эта была slave база относительно master базы MODX.
По опыту разработки очень и ОЧЕНЬ больших систем в которых данных было по 10 млн строк, могу сказать что первый perfomance issue который вы можете увидеть это сборка какой-либо отчетности с мастер базы. Это супер плохая практика. Такие манипуляции лучше всегда производить на уровне slave базы. А еще лучше — использовать специализированные сервисы для сбора репортов.
Еще одно PS: Для master/slave синхронизации лучше использовать бинарно совместимую MariaDB/Persona Server вместо MySQL. А еще лучше начинать все новые проекты сразу на Postgres. Ваши проекты скажут мне спасибо за совет :)
Итак, отвлеклись и ладно
Откроем файл

Укажите DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD подключения к серверу MySQL.
Например, получится вот так

Далее нам необходимо сгенерировать модели для работы с MODX сущностями внутри Laravel. Можно использовать конечно же просто фасад DB и писать сырые запросы. Однако, мне бы хотелось использовать scopes и прочие радости жизни, поэтому настаиваю именно на моделях :)
Откроем консоль и установим пакетик который позволяем нам это сделать с помощью одной кнопки.
А после этого выполним artisan команду которая сгенерируют нам все модели доступные в MODX базе.
Результатом выполнения будет наличие в папке Models целого полотна моделей

Генерировать все модели, наверное, избыточно. Однако, вдруг они нам зачем-то понадобятся.
Прелесть этого пакета для синхронизации в том, что он генерирует модели согласно всем Laravel правилам и fillable и даже кастами.
Давайте глянем на модель продуктовых опций которая у нас получилась

Красота!
Давайте че-нить пофильтурем.
Invokable контроллер мы создаем так как планируем использовать только один тип запроса — GET. Нет смысла генерировать полноценный контроллер с другими методами.

Так же откроем файлик api.php и расскажем нашему Laravel приложению чтобы он следил за нашим контроллером.

Мы готовы для того чтобы писать код!

Давайте представим тот факт, что вы и без меня знаете как создать валидаторы и прочее. Я не буду останавливаться на этой теме.
Давайте создадим абстрактный класс нашего фильтра. В нем реализуем запрос, перебор всех их значений. Если у фильтра есть метод с параметром запроса, то мы вызовем метод и передадим ему значение.
Далее нам потребуется фактическая реализация этого фильтра, которая будет имплементировать этот фильтр. Для этого создадим класс ProductOptionFilter
Я не зря сказал что хочу использовать именно модели, а не фасад DB. Лучший способ фильтрации — указать его в скоупе. Давайте создадим трейд, который мы будем использовать в наших моделях.

Вернемся в контроллер!
Для того чтобы вызвать фильтр, нам достаточно вернуть что-то вроде вот такого:
Пример ответа будет следующий:

У нас теперь появился сервис, который может фильтровать (и эти фильтры вы можете писать сами).
Для того чтобы использовать его в MODX достаточно вызывать этот фильтр из под JS и в ответе от сервера получить айтишники товаров, которые вы можете подсунуть сниппету msProduct
Возможно когда-нибудь я сделаю полноценный микросервис с поддержкой слайдеров (значений от и до) и прочего.
Но пока как-то лень.
Всем спасибо!
Давайте представим что у вас есть интернет-магазин на MODX + miniShop2 с очень большим кол-вом товаров и каким-никаким хайлоадом :)
Использование встроенных фильтров в mSearch вас немного ограничивает. Как минимум тем, что не кеширует результат.
Предлагаю припилить к нашему любимому MODX небольшой микросервис на Laravel который будет фильтровать данные по входному запросу, а также отдавать нам список айдишников которые подходят под выборку.
Создадим новый Laravel проект
Предполагается что вы знаете что такое composer, как его установить и в целом умеете пользоваться базовым PHP
composer create-project laravel/laravel laravel_filtersДанная команда создаст вам новый Laravel проект в папке laravel_filters.
В Laravel есть единый файл конфигурации в котором можно указать подключения к различным сервисам.
Чисто для примера мы будем использовать ту же самую базу что использует MODX. Однако, на практике я бы предпочитал чтобы эта была slave база относительно master базы MODX.
По опыту разработки очень и ОЧЕНЬ больших систем в которых данных было по 10 млн строк, могу сказать что первый perfomance issue который вы можете увидеть это сборка какой-либо отчетности с мастер базы. Это супер плохая практика. Такие манипуляции лучше всегда производить на уровне slave базы. А еще лучше — использовать специализированные сервисы для сбора репортов.
Еще одно PS: Для master/slave синхронизации лучше использовать бинарно совместимую MariaDB/Persona Server вместо MySQL. А еще лучше начинать все новые проекты сразу на Postgres. Ваши проекты скажут мне спасибо за совет :)
Итак, отвлеклись и ладно
Откроем файл
.env в корне нашего Laravel проекта и поменяем данные подключения на те же самые что мы использовали при установке MODX
Укажите DB_HOST, DB_DATABASE, DB_USERNAME, DB_PASSWORD подключения к серверу MySQL.
Например, получится вот так

Далее нам необходимо сгенерировать модели для работы с MODX сущностями внутри Laravel. Можно использовать конечно же просто фасад DB и писать сырые запросы. Однако, мне бы хотелось использовать scopes и прочие радости жизни, поэтому настаиваю именно на моделях :)
Откроем консоль и установим пакетик который позволяем нам это сделать с помощью одной кнопки.
composer require reliese/laravel --devА после этого выполним artisan команду которая сгенерируют нам все модели доступные в MODX базе.
php artisan code:modelsРезультатом выполнения будет наличие в папке Models целого полотна моделей

Генерировать все модели, наверное, избыточно. Однако, вдруг они нам зачем-то понадобятся.
Прелесть этого пакета для синхронизации в том, что он генерирует модели согласно всем Laravel правилам и fillable и даже кастами.
Давайте глянем на модель продуктовых опций которая у нас получилась

Красота!
Давайте че-нить пофильтурем.
php artisan make:controller "API\v1\ProductOptonsControler" --invokableInvokable контроллер мы создаем так как планируем использовать только один тип запроса — GET. Нет смысла генерировать полноценный контроллер с другими методами.

Так же откроем файлик api.php и расскажем нашему Laravel приложению чтобы он следил за нашим контроллером.

Мы готовы для того чтобы писать код!

Давайте представим тот факт, что вы и без меня знаете как создать валидаторы и прочее. Я не буду останавливаться на этой теме.
Базовый класс фильтра
Давайте создадим абстрактный класс нашего фильтра. В нем реализуем запрос, перебор всех их значений. Если у фильтра есть метод с параметром запроса, то мы вызовем метод и передадим ему значение.
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
abstract class Filter
{
protected Request $request;
protected Builder $builder;
public function __construct(Request $request)
{
$this->request = $request;
}
public function apply(Builder $builder) : Builder
{
$this->builder = $builder;
foreach ($this->request->all() as $name => $value)
{
if (method_exists($this, $name))
{
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
}Класс фильтрации
Далее нам потребуется фактическая реализация этого фильтра, которая будет имплементировать этот фильтр. Для этого создадим класс ProductOptionFilter
<?php
namespace App\Filters;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Schema;
class ProductOptionFilter extends Filter
{
/**
* Filter the products by the given string.
*
* @param string|null $value
* @return \Illuminate\Database\Eloquent\Builder
*/
public function key(array $params): Builder
{
foreach ($params as $key => $value)
{
$this->builder->where('key',$key)
->where('value', $value);
}
return $this->builder;
}
}Если вы хотите добавить новую логику фильтрации, все, что вам нужно сделать, это создать новый метод и реализовать внутри него логику построения запросов. С этого момента наш класс filter готов принять новый параметр и его значение.Поможем нашей модели стать фильтруемой :)
Я не зря сказал что хочу использовать именно модели, а не фасад DB. Лучший способ фильтрации — указать его в скоупе. Давайте создадим трейд, который мы будем использовать в наших моделях.
<?php
namespace App\Traits;
use App\Filters\Filter;
use Illuminate\Database\Eloquent\Builder;
trait Filterable
{
/**
* Apply all relevant filters.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \App\Filters\Filter $filter
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeFilter(Builder $query, Filter $filter): Builder
{
return $filter->apply($query);
}
}А так же обновим нашу модель ModxMs2ProductOption чтобы она могла получить нужный скоуп.
Вернемся в контроллер!
Для того чтобы вызвать фильтр, нам достаточно вернуть что-то вроде вот такого:
<?php
namespace App\Http\Controllers\API\v1;
use App\Filters\ProductOptionFilter;
use App\Http\Controllers\Controller;
use App\Models\ModxMs2ProductOption;
use Illuminate\Http\Request;
class ProductOptonsControler extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(ProductOptionFilter $filter)
{
return ModxMs2ProductOption::filter($filter)->paginate(10);
}
}Поднять ваше приложение вы сможете с помощью командыphp artisan serveПример ответа будет следующий:

Итак, шо мы наделали вообще?
У нас теперь появился сервис, который может фильтровать (и эти фильтры вы можете писать сами).
Для того чтобы использовать его в MODX достаточно вызывать этот фильтр из под JS и в ответе от сервера получить айтишники товаров, которые вы можете подсунуть сниппету msProduct
Pro tips
- Ответы от сервера можно кешировать и разгрузить базу в принципе.
Однако не забудьте тот факт, что кеш нужно когда-то очищать!
<?php
namespace App\Http\Controllers\API\v1;
use App\Filters\ProductOptionFilter;
use App\Http\Controllers\Controller;
use App\Models\ModxMs2ProductOption;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
class ProductOptonsControler extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(ProductOptionFilter $filter)
{
$cacheKey = http_build_query(request()->key);
$expire = Carbon::now()->addHours(24);
return Cache::remember($cacheKey, $expire, function () use ($filter) {
return ModxMs2ProductOption::filter($filter)->paginate(10);
});
}
}- Возвращать только те товары которые есть в наличии.
Для этого вам нужно заджойнить табличку с продуктами и сделать необходимые выборки.
Т.к наличие товара в каждом интернет-магазине сделано по разному, то пример приводить не буду.
Надеюсь поле «В наличии» в minishop припилят :)
- Использовать совершенно разные БД.
Вам никто не мешает создать свою структуру в вашем Laravel приложении и сделать общение между вашим сайтом и приложением с помощь DbSync.
В целом — тоже решение.
Возможно когда-нибудь я сделаю полноценный микросервис с поддержкой слайдеров (значений от и до) и прочего.
Но пока как-то лень.
Всем спасибо!
Комментарии: 27
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
Пожалуйста, тесты скорости в студию!
В общем я ничего не понял, но очень интересно)))
Получишь, что-то вроде mSearch на выходе.
Конечно нет :). Если нужен совместимый с MySQL — вам к MariaDB.
В основном они завязаны на вопросе масштабируемости.
В MySQL очень много вещей попросту платные либо нерешаемые относительно постгреса.
Сдаётся мне, что весь сыр-бор с передачей функционала mSearch2 «на сторону» разгорелся вокруг нехватки ресурсов хоста, на котором крутится магазин + не оптимальной настройке сайта. Хотя, я достаточно давно от Modx отошёл, может быть, не совсем верно понял проблему.
У меня на поддержке осталось несколько магазинов, например:
1) 96000 товаров и 12 фильтров
2) 9000 и 55 фильтров (индексы в мускуле для msProductData уже закончились) :-)
modhost на максимальном тарифе, проблем нет, всё и всегда нормально работает, каких-то заметных задержек в фильтрах нет. Все свойства реализованы расширением msProductData.
Я не очень понимаю, чего может не хватать в msSearch2 разработчику, который работает с modx.
Если делаем «серьёзный» магазин и не устраивает msSearch2, то, очевидно, что и modx тут как-то совсем не в тему. Берём туже ларку с эластик. Ну и немного команду разрабов добавляем к себе на зарплату.
Всё имеет свою цену.
К вопросу же «серьёзности»: второй магазин — лидер рынка в «СНГ» в весьма живом сегменте с огромным оборотом. MODX+mSearch2 обеспечивает сайту нормальную работу уже лет 10.
И там за это время столько всего понаделано, аж страшно иногда. Там уже CRM по сути вместо сайта.
И ничего, всё ок.
С другой стороны, сейчас наблюдаю поневоле много сайтов на WP/Bitrix и т.п.
Во где ужас-то в большинстве случаев ) На большинстве таких сайтов я бы никогда ничего не заказал.
Так что на MODX и mSearch2 вполне можно и даже нужно клепать сайты. Только настраивать более-менее и хостинг не совсем бюджетный брать.
Ставить крест на MODX/mSearch2 я бы вообще не стал.
Сейчас, кмк, MODX не хватает продвижения в ru + выпуск 3 версии в момент фактического схлопывания сообщества сильно подгадил.
Вообще говоря для каталогов с больше 1500 товаров по умолчанию отключается suggestions и скорость значительно выше. Но фильтр значительно не удобнее.
1) какой сервер?
2) сколько товаров на сайте и сколько в каталоге?
3) Включенно ли suggestions?
4) Логи mFilter2 что показывают?
Пробуйте. Свой опыт я описал.
Кешируем mFilter2
Максимальный 1 сайт, 10 доменов 16 Gb 60 сек. 512 Mb 10 600 / мес.
6 570 / год
Этот тариф модхост подходит под максимальный? На котором тормоза должны уйти? В принципе 600р могу потратить :-). С базой товаров посложнее, но вроде на 37000 товаров могу договориться чтоб предоставили.
Следующий тариф 1 200р и уже дорого.
Я просто написал пост «о многом, о разном», мой опыт. Тестов и цифр не будет (тупо некогда), мой пост не про конкретные цифры/доказательства чего-то и т.п. Можно ему просто не верить.
1) Зачем тут использовать laravel? Какие он преимущества даст, кроме собственного удовлетворения что теперь то «все красиво»
2) В быстрой фильтрации важнее правильные индексы и в принципе архитектура бд, laravel на это никак не повлияет
3) «Микросервисы» на PHP сложно назвать микросевисами, хотя бы потому, что они обмениваются по json api (вместо gRPC например) который медленный и сильно нагружает сеть, я уже молчу о том, что сам laravel сильно тяжелее того же modx
По мне лучше бы показал как интегрировать какой нибудь легковесный полнотекстовой поиск, по типу meilisearch и на основе него уже построить фильтрацию, а уже что там будешь использовать для обращения к api meilisearch laravel, modx или нативный php уже не важно
Так хотя бы профит будет
Тоже не понял, зачем тут Laravel. Ради Eloquent? Так его можно и к MODX прикрутить)
Если уж микросервис, то я бы Slim использовал.
Боюсь что он будет даже отрицательный, нежели положительный
Не меняя подход к выборке (например параллельные запросы), не меняя архитектуру базы данных, не проставляя индексы какое время вы хотите выиграть? А прослойка в виде api скорее всего только тормозит результаты
Оооо… тут вообще можно бесконечно рассуждать, говоря о кешировании, вы как, батенька кешируете? Например если ты кешируешь в файловую систему, то, забрать из fs будет сильно дороже по ресурсам и времени, нежели забрать из БД с правильными индексами. По другому обстоят дела если это memcached или redis, но тут будут и другие подводные камни, мы же говорим о базовой реализации, не так ли?
а чем тяжелее, можно подробнее?)
Та же eloquent содержит в себе сильно больше логики и обвязки (потому что хочет быть похожей на ORM, но ей не является) чем пусть и кривоватый, но конструктор запросов под названием pdoTools
а почему не является?
ну это искусственный тест, а на реальных проектах заметно?
тык
Исходя из этого бенчмарка Lucinda быстрее laravel в 47 раз
За что я ненавижу Eloquent ORM а также тысчи подобных статей и обсуждений к ним
Конечно еще сильнее становится заметно чем обычный hello world, чем больше связей, чем больше данных тем более заметно, но это все не критично, laravel — идеальный php фреймворк, его выбирают за простоту работы, за то, насколько просто найти разработчика на него и насколько быстро можно реализовывать фичи, если недостаточно laravel'я, то стоит задуматься не о других фреймворках, а о других ЯП уже