Pull to refresh

Comments 15

Т.к. вы используете webonyx — четвёртым аргументом анонимочки оного идёт ResolveInfo объект, так что простым редьюсером по полям, которые возвращает $info->getFieldSelection() можно получить список всех релейшенов и полей отдельно, остаётся просто построить запрос, вида (т.к. судя по коду у вас Eloquent): DB::table('articles')->with($info->relations())->... и можно получить жадную связь на месте почти без лапши в коде.

С другой стороны можно воспользоваться подходом "единой точки входа", как делал у себя я и разбить связи на элементы корутины, делая что-то вроде:


Cпойлер
class ExampleQuery extends AbstractQuery
{
    public function resolve(Query $query): iterable
    {
        /**
         * >>
         *
         * query {
         *   example {
         *     relation {
         *       id
         *     }
         *   }
         *   example_2 {
         *      id
         *   }
         * }
         *
         * <<
         *
         * $relations = [
         *     'example' => [
         *          'relation'
         *      ],
         *     'example_2'
         * ]
         */
        $relations = $query->getRelations();

        $q = \DB::table('articles');

        yield 'example' => function(...) use ($q) { $q->with('example'); };

        yield 'example.relation' => function(...) { ... };

        yield 'example_2' => function(...) { ... };

        return $q->get();
    }
}

ну к примеру… Тоже самое касается и аргументов типов.


Возможно это подтолкнёт это к каким-нибудь идям по улучшению вашей разработки =)

Идея интересная, но к сожалению реализация мне не совсем понятна. Наверное мой уровень знания PHP недостаточно велик. ) Не могли бы вы привести пример использования данного класса? Или еще лучше было бы посмотреть код какого-либо проекта с использованием вашего подхода целиком, если такой есть в открытом доступе разумеется.

Как раз этот пункт с подобными запросами в данный момент прорабатываю, так что привести пример будет проблематично, т.к. его просто нет. Но в целом, некоторое ядро с небольшим описанием готово. Не знаю что из этого может быть интересно, но вдруг: https://github.com/SerafimArts/Railgun (там есть неполная документация с примерами).

Спасибо.
Я смотрю у вас любопытная библиотека готовится. Буду следить. )
В данной статье я решил не использовать какой-либо конкретный фреймворк, поэтому то, что похоже на Eloquent скорее является псевдокодом и сделано так, чтобы на мой взгляд было проще понять проблему.
А вот уже на реальных проектах ваш совет будет очень полезен, про ResolveInfo я знаю, но таким образом его не использовал. Спасибо, возьму на вооружение. )
Попробую внести замечание:
Самый распространенный метод решения этой проблемы — это группировка запросов.

А самый правильный — это JOIN двух таблиц — один запрос и оптимизатор СУБД сделает всю работу за Вас.
Я же надеюсь Вы с SQL Injection боретесь при помощи плейсхолдеров в запросе и передачи параметров отдельно, а не кучей HTML/XML/SQL-encode-ров над строкой в которую конкатенируете параметры от пользователя?
Я же надеюсь Вы с SQL Injection боретесь при помощи плейсхолдеров...

В двух предыдущих статьях я делал оговорку, что для простоты примера я не заморачиваюсь с безопасностью, в этой же решил не повторять ее в третий раз. И видимо зря. ) Разумеется на живом проекте так делать ни в коем случае нельзя.

А самый правильный — это JOIN двух таблиц

Разумеется вы можете написать более сложный запрос в ресолвере поля «allUsers», добавив в него JOIN. И тогда вам даже не надо будет писать ресолвер для поля «countFriends».
Я же намеренно усложнил себе задачу, чтобы «искусственно» создать себе проблему, в которой можно будет рассказать про Deferred. Главное вы знаете что делать если в один запрос уложиться не получается, а все остальное в ваших руках.
1. В GraphQL у вас клиент может запросить поле «author», а может и не запросить. Вы будете на сервере всегда делать джоин? А если подобных полей 10?

Чтоб это хоть как-то работало — придется смотреть, какие поля пользователь запрашивал, а какие — нет и в зависимости от этого строить разные запросы. Что существенно увеличит сложность системы.

2. В реальных проектах, которые чуть сложнее, чем дважды-два бывают уровни кэшей, бывают поисковые индексы, графовые индексы и т.д. и т.п. И та же проблема N+1 для них тоже актуальна.

Собственно, тот же Фейсбук не случайно использует именно подход, описанный в статье (см их проект dataloader).

Поэтому join'ы смогут эффективно решить лишь очень небольшое подмножество задач. Если этого хватает — отлично, но в общем случае на одних join'ах далеко не уедешь.
ОК. Если была цель «сотворить искусственную проблему» что бы показать возможность другого инструмента или алгоритма- то тогда в учебно-демонстративных целях принимается ;)
Извините, может, я чего не понимаю, но почему не использовать подготовленный (prepared) запрос, который в цикле просто выполняется (execute) с параметром, соответствующим айдишнику статьи? правильности джоина это не отменяет, просто не понял, почему нет этого решения в статье :).
В качестве основы для статьи я взял код написанный в предыдущих частях статьи. И там все делалось примерно так как вы написали. В этой же статье я постарался изменить код, чтобы уменьшить количество запросов (количество выполнений execute). В цикле для запроса данных о 30 пользователях вы бы отправили 30 запросов, а после описанных мною манипуляций вы бы сделали это за 1 запрос.
А нужно это для того чтобы снизить нагрузку на сервер БД и уменьшить время выполнения. Так как выполнить 1 запрос быстрее чем 30.
Спасибо за статью. Мы используем deffered вот так:
    public static function resolveSomething(UserModel $user, array $args, AppContext $context, ResolveInfo $info) : Deferred
    {
        $context->lifecycleData['something'][$user->getId()] = $user->getId();  // собираем все id
        return new Deferred(function () use ($context, $user) {
            // внутри getByUsers есть runtime cache, который делает запрос к БД только 1 раз
            return SomethingRepository::getByUsers($context->lifecycleData['something'])[$user->getId()];
        });
    }
Не сразу понял фразу «отлаживать выполнение ресолверов». Думал, это что-то связанное с отладкой. Пожалуйста, поменяйте на «откладывать»
Спасибо. Поправил и даже дополнил немного.
Sign up to leave a comment.

Articles