Давайте пофильтруем данные с помощью микросервиса

Драма по поводу хорош или плох mSearch2 не утихает. Многие пользователи системы видят ее исключительно в монолитом виде что очень сильно ограничивает кейс системы. Причем любой.

Давайте представим что у вас есть интернет-магазин на 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" --invokable


Invokable контроллер мы создаем так как планируем использовать только один тип запроса — 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



  1. Ответы от сервера можно кешировать и разгрузить базу в принципе.
    Однако не забудьте тот факт, что кеш нужно когда-то очищать!
В качестве ключа можем использовать наш запрос и по умолчанию кешировать на день.
<?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);
        });
    }
}
  1. Возвращать только те товары которые есть в наличии.
    Для этого вам нужно заджойнить табличку с продуктами и сделать необходимые выборки.
    Т.к наличие товара в каждом интернет-магазине сделано по разному, то пример приводить не буду.
    Надеюсь поле «В наличии» в minishop припилят :)
  2. Использовать совершенно разные БД.
    Вам никто не мешает создать свою структуру в вашем Laravel приложении и сделать общение между вашим сайтом и приложением с помощь DbSync.
    В целом — тоже решение.
Вот такие прикольчики я вам придумал.
Возможно когда-нибудь я сделаю полноценный микросервис с поддержкой слайдеров (значений от и до) и прочего.
Но пока как-то лень.
Всем спасибо!
bears
Павел Бигель
20 декабря 2022, 16:23
modx.pro
4
1 438
+15
Поблагодарить автора Отправить деньги

Комментарии: 27

Дима Касаткин
20 декабря 2022, 17:01
1
0
Спасибо за статью! Страшновато, но захватывающе!

Пожалуйста, тесты скорости в студию!
    Артур Шевченко
    21 декабря 2022, 00:31
    0
    Очень интересно было прочитать про возможности Laravel. Только я так и не понял зачем он (Laravel) тут? ПОчему нельзя тоже самое написать на чистом PHP + API Modx? Это гораздо сложнее? Работать будет медленнее? Чем данный пример принципиально отличается от обычной подстановки значения в SQL запрос?

    В общем я ничего не понял, но очень интересно)))
      Павел Бигель
      21 декабря 2022, 01:03
      0
      ПОчему нельзя тоже самое написать на чистом PHP + API Modx?
      Почему же, можно.
      Получишь, что-то вроде mSearch на выходе.
      Александр Мельник
      21 декабря 2022, 09:03
      0
      А еще лучше начинать все новые проекты сразу на Postgres. Ваши проекты скажут мне спасибо за совет :)
      Поделитесь пожалуйста, какие преимущества Postgres лично вы можете выделить. Не абстрактные, а те с которыми реально столкнулись. Приходилось ли работать с postgress без прослойки ORM, а напрямую на SQL? Синтаксис стопроцентно совпадает с mysql?
        Павел Бигель
        21 декабря 2022, 10:12
        0
        Приходилось ли работать с postgress без прослойки ORM, а напрямую на SQL?
        Ну разве что не в PHP проектах.
        Синтаксис стопроцентно совпадает с mysql?
        Конечно нет :). Если нужен совместимый с MySQL — вам к MariaDB.

        Поделитесь пожалуйста, какие преимущества Postgres лично вы можете выделить
        В основном они завязаны на вопросе масштабируемости.
        В MySQL очень много вещей попросту платные либо нерешаемые относительно постгреса.
        Александр Туниеков
        21 декабря 2022, 17:54
        +3
        Чудесные тут методы пишут :-). Проблема не в том чтобы отфильтровать по какому-то определенному значению. С этим и mysql легко справляется. Вопрос как получить список значений по каким можно фильтровать? И как получить кол-во ресурсов для этих значений? И как получить кол-во ресурсов для этих значений если какое-то значение уже выбрано
          Алексей Шумаев
          21 декабря 2022, 20:48
          +3
          Инструкция полезная по-любому, хотя бы для расширения кругозора.

          Сдаётся мне, что весь сыр-бор с передачей функционала 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 версии в момент фактического схлопывания сообщества сильно подгадил.
            Александр Туниеков
            22 декабря 2022, 07:35
            0
            У меня на поддержке осталось несколько магазинов, например:
            1) 96000 товаров и 12 фильтров
            2) 9000 и 55 фильтров (индексы в мускуле для msProductData уже закончились) :-)
            modhost на максимальном тарифе, проблем нет, всё и всегда нормально работает, каких-то заметных задержек в фильтрах нет. Все свойства реализованы расширением msProductData.
            Ну я вот на производительных серверах не тестировал. Интересно насколько влияет проиводительность сервера.
            Вообще говоря для каталогов с больше 1500 товаров по умолчанию отключается suggestions и скорость значительно выше. Но фильтр значительно не удобнее.
              Алексей Шумаев
              22 декабря 2022, 11:11
              0
              Насколько я помню, 1500 товаров нормально работают на мин тарифе бегета с suggestions. Если фильтров не очень много, конечно. Таких сайтов в своё время море наделано было.
                Александр Туниеков
                22 декабря 2022, 15:16
                0
                1500 товаров у меня нормально работают. Тормоза начинаются с 4000 товаров на мин тарифе. А вот на производительных серверах не понятно.
                  Алексей Шумаев
                  22 декабря 2022, 15:49
                  0
                  Нормально всё будет
                    Александр Туниеков
                    22 декабря 2022, 15:56
                    0
                    Тесты надо и чтоб подробные отчеты:
                    1) какой сервер?
                    2) сколько товаров на сайте и сколько в каталоге?
                    3) Включенно ли suggestions?
                    4) Логи mFilter2 что показывают?
                      Алексей Шумаев
                      22 декабря 2022, 16:04
                      0
                      Нормально так ) Это я должен сделать?
                      Пробуйте. Свой опыт я описал.
                        Александр Туниеков
                        22 декабря 2022, 16:14
                        0
                        Ну может вы где-то что-то не догоняете :-) у меня один опыт и я цифры в заметке привел.
                        Кешируем mFilter2
                        Максимальный 1 сайт, 10 доменов 16 Gb 60 сек. 512 Mb 10 600 / мес.
                        6 570 / год
                        Этот тариф модхост подходит под максимальный? На котором тормоза должны уйти? В принципе 600р могу потратить :-). С базой товаров посложнее, но вроде на 37000 товаров могу договориться чтоб предоставили.

                        Следующий тариф 1 200р и уже дорого.
                          Алексей Шумаев
                          22 декабря 2022, 16:22
                          0
                          Да, не догоняю )
                          Я просто написал пост «о многом, о разном», мой опыт. Тестов и цифр не будет (тупо некогда), мой пост не про конкретные цифры/доказательства чего-то и т.п. Можно ему просто не верить.
            Pavel Zarubin
            22 декабря 2022, 10:08
            +3
            Странное решение честно говоря
            1) Зачем тут использовать laravel? Какие он преимущества даст, кроме собственного удовлетворения что теперь то «все красиво»
            2) В быстрой фильтрации важнее правильные индексы и в принципе архитектура бд, laravel на это никак не повлияет
            3) «Микросервисы» на PHP сложно назвать микросевисами, хотя бы потому, что они обмениваются по json api (вместо gRPC например) который медленный и сильно нагружает сеть, я уже молчу о том, что сам laravel сильно тяжелее того же modx

            По мне лучше бы показал как интегрировать какой нибудь легковесный полнотекстовой поиск, по типу meilisearch и на основе него уже построить фильтрацию, а уже что там будешь использовать для обращения к api meilisearch laravel, modx или нативный php уже не важно
            Так хотя бы профит будет
              deleted
              22 декабря 2022, 13:08
              0
              Использование встроенных фильтров в mSearch вас немного ограничивает.
              А чем ограничивает? Ни разу mSearch2 не использовал, любопытно просто.

              Тоже не понял, зачем тут Laravel. Ради Eloquent? Так его можно и к MODX прикрутить)
                Алексей Шумаев
                22 декабря 2022, 13:19
                0
                Насколько я понял: ларавель тут просто для примера, ибо не суть важно.
                Если уж микросервис, то я бы Slim использовал.
                Дима Касаткин
                22 декабря 2022, 14:33
                0
                Так хотя бы профит будет
                Да брось, профит скорее всего есть, только тестов нет… А чтобы это исправить надо всё это поднять, попробуем-с…
                  Pavel Zarubin
                  22 декабря 2022, 14:38
                  0
                  профит скорее всего есть
                  Хотелось бы услышать, какой?
                  Боюсь что он будет даже отрицательный, нежели положительный
                  Не меняя подход к выборке (например параллельные запросы), не меняя архитектуру базы данных, не проставляя индексы какое время вы хотите выиграть? А прослойка в виде api скорее всего только тормозит результаты
                    Павел Бигель
                    22 декабря 2022, 15:11
                    0
                    отрицательный когда фильтрация падает не на мастер базу, а на slave еще и кешированием?)
                      Pavel Zarubin
                      22 декабря 2022, 15:33
                      0
                      а на slave
                      А как slave должен ускорить?
                      еще и кешированием
                      Оооо… тут вообще можно бесконечно рассуждать, говоря о кешировании, вы как, батенька кешируете? Например если ты кешируешь в файловую систему, то, забрать из fs будет сильно дороже по ресурсам и времени, нежели забрать из БД с правильными индексами. По другому обстоят дела если это memcached или redis, но тут будут и другие подводные камни, мы же говорим о базовой реализации, не так ли?
                  deleted
                  22 декабря 2022, 14:53
                  0
                  > что сам laravel сильно тяжелее того же modx

                  а чем тяжелее, можно подробнее?)
                    Pavel Zarubin
                    22 декабря 2022, 15:03
                    0
                    На тот момент, когда я ушел в laravel (это была еще 6.х версия) modx показывал на голой странице с выборкой из бд 1000 элементов гораздо меньше потребления и по памяти и по CPU, как сейчас дела обстоят не знаю, но подозреваю что +- также, да это и не удивительно, laravel из коробки содержит логики в несколько раз больше, чем modx, да и PSR ООП само по себе тяжелее, нежели легаси modx
                    Та же eloquent содержит в себе сильно больше логики и обвязки (потому что хочет быть похожей на ORM, но ей не является) чем пусть и кривоватый, но конструктор запросов под названием pdoTools
                      Pavel Zarubin
                      22 декабря 2022, 15:09
                      0
                      Вообще, думаю не будет ни для кого откровением, что laravel — один из самых медленных php фреймворков, он ориентируется не на скорость, он ориентируется на пользователей и простоту использования и ради этого жертвует скоростью
                        deleted
                        22 декабря 2022, 16:11
                        0
                        Вообще, думаю не будет ни для кого откровением, что laravel — один из самых медленных php фреймворков
                        ну для меня будет)

                        хочет быть похожей на ORM, но ей не является
                        а почему не является?

                        modx показывал на голой странице с выборкой из бд 1000 элементов гораздо меньше потребления и по памяти и по CPU
                        ну это искусственный тест, а на реальных проектах заметно?
                          Pavel Zarubin
                          22 декабря 2022, 16:29
                          +1
                          ну для меня будет)
                          Достаточно просто загуглить «php framework banchmarks»
                          тык
                          Исходя из этого бенчмарка Lucinda быстрее laravel в 47 раз

                          а почему не является?
                          За что я ненавижу Eloquent ORM а также тысчи подобных статей и обсуждений к ним

                          ну это искусственный тест, а на реальных проектах заметно?
                          Конечно еще сильнее становится заметно чем обычный hello world, чем больше связей, чем больше данных тем более заметно, но это все не критично, laravel — идеальный php фреймворк, его выбирают за простоту работы, за то, насколько просто найти разработчика на него и насколько быстро можно реализовывать фичи, если недостаточно laravel'я, то стоит задуматься не о других фреймворках, а о других ЯП уже
                    Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                    27