Комментарии 15
Т.к. вы используете webonyx
— четвёртым аргументом анонимочки оного идёт ResolveInfo объект, так что простым редьюсером по полям, которые возвращает $info->getFieldSelection()
можно получить список всех релейшенов и полей отдельно, остаётся просто построить запрос, вида (т.к. судя по коду у вас Eloquent): DB::table('articles')->with($info->relations())->...
и можно получить жадную связь на месте почти без лапши в коде.
С другой стороны можно воспользоваться подходом "единой точки входа", как делал у себя я и разбить связи на элементы корутины, делая что-то вроде:
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();
}
}
ну к примеру… Тоже самое касается и аргументов типов.
Возможно это подтолкнёт это к каким-нибудь идям по улучшению вашей разработки =)
Как раз этот пункт с подобными запросами в данный момент прорабатываю, так что привести пример будет проблематично, т.к. его просто нет. Но в целом, некоторое ядро с небольшим описанием готово. Не знаю что из этого может быть интересно, но вдруг: https://github.com/SerafimArts/Railgun (там есть неполная документация с примерами).
А вот уже на реальных проектах ваш совет будет очень полезен, про ResolveInfo я знаю, но таким образом его не использовал. Спасибо, возьму на вооружение. )
Самый распространенный метод решения этой проблемы — это группировка запросов.
А самый правильный — это JOIN двух таблиц — один запрос и оптимизатор СУБД сделает всю работу за Вас.
Я же надеюсь Вы с SQL Injection боретесь при помощи плейсхолдеров в запросе и передачи параметров отдельно, а не кучей HTML/XML/SQL-encode-ров над строкой в которую конкатенируете параметры от пользователя?
Я же надеюсь Вы с SQL Injection боретесь при помощи плейсхолдеров...
В двух предыдущих статьях я делал оговорку, что для простоты примера я не заморачиваюсь с безопасностью, в этой же решил не повторять ее в третий раз. И видимо зря. ) Разумеется на живом проекте так делать ни в коем случае нельзя.
А самый правильный — это JOIN двух таблиц
Разумеется вы можете написать более сложный запрос в ресолвере поля «allUsers», добавив в него JOIN. И тогда вам даже не надо будет писать ресолвер для поля «countFriends».
Я же намеренно усложнил себе задачу, чтобы «искусственно» создать себе проблему, в которой можно будет рассказать про Deferred. Главное вы знаете что делать если в один запрос уложиться не получается, а все остальное в ваших руках.
Чтоб это хоть как-то работало — придется смотреть, какие поля пользователь запрашивал, а какие — нет и в зависимости от этого строить разные запросы. Что существенно увеличит сложность системы.
2. В реальных проектах, которые чуть сложнее, чем дважды-два бывают уровни кэшей, бывают поисковые индексы, графовые индексы и т.д. и т.п. И та же проблема N+1 для них тоже актуальна.
Собственно, тот же Фейсбук не случайно использует именно подход, описанный в статье (см их проект dataloader).
Поэтому join'ы смогут эффективно решить лишь очень небольшое подмножество задач. Если этого хватает — отлично, но в общем случае на одних join'ах далеко не уедешь.
А нужно это для того чтобы снизить нагрузку на сервер БД и уменьшить время выполнения. Так как выполнить 1 запрос быстрее чем 30.
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()];
});
}
Делаем GraphQL API на PHP и MySQL. Часть 3: Решение проблемы N+1 запросов