Comments 33
1. Это решается на уровне приклада, тут можно придумать множество или своих реализаций по распределению доступа, или использовать готовые реализации.
2. С обработкой ошибок и правда немного сложновато… но это издержки жесткой типизации данных. Для этого GraphQL умеет отдавать свою текущую структуру говоря какое поле какого типа чтобы программист не допускал подобных ошибок.
3. Для решения данной проблемы не обязательно использовать библиотеку от Facebook. Тут опять же все зависит от фантазии программиста. Тут даже есть хорошая статья на эту тему — habr.com/ru/post/329408
не обязательно использовать библиотеку facebook
В указанной Вами статье и пользуется библиотека graphql-php в которой реализован функционлал со ссылкой на аналогичное решение dataloader от facebook. webonyx.github.io/graphql-php/data-fetching/#solving-n1-problem Ничего принципиально другого там не реализовано.
В обсуждениях возникала ссылка на эликсировскую библиотеку github.com/absinthe-graphql/absinthe которая якобы откладывает запрос более кардинальным образом и генерирует единый запрос с SQL JOIN. Но подтвердить это не могу.
1. Он не должен решать проблему авторизации. Хотите авторизацию, пишите свои или используйте готовые механизмы.
2. Так же как и в REST-е можно вернуть нужный текст ошибки, только вместо кодов ошибки есть типы ошибок — ошибка GraphQL или Network ошибка. Что ещё нужно?
3. Ну используй DataLoader-ы.
По поводу JOIN-ов — ну извините, одна дата модель-один запрос, по другому будут костыли.
К тому же у 99% пользователей никаких проблем с этим не будет.
Оставшийся 1% — не используйте графкл или переходите с sql на графовую базу, как у фейсбука.
вместо кодов ошибок есть типы ошибок
Ничего подобного в спецификации graphql нет см graphql.github.io/graphql-spec/June2018/#sec-Errors
Кроме message ещё можно узнать путь и номер строки. Тип ошибки это уже в реализациях сервера и клиента. Если использовать apollo-server то можно ещё много чего добавить в ошибку. Но только если дело дошло до резольвера. Если ошибка была на этапе валидации ее содержание будет стандартное.
Проблема SELECT N + 1 решена в таких штуках как join-monster, postgraphile, prisma и тп. Они даже полей лишних не выбирают. Но dataloader тоже не плох.
А так завожу пользователя под каждую роль, условно admin/user/guest + current_setting('user_id')::int (set role, set user_id перед запросом). Это подход не лично мой, я узнал его из доков postgraphile/postgREST и начал использовать везде — удобно.
Т.е роли для грубой настройки и where/policies для тонкой
create view my_posts as
select *
from posts
where author_id = current_setting('user_id');
grant select on my_posts to my_app_user;
grant delete on my_posts to my_app_admin;
С row level security чуть сложнее, но еще точнее можно. И еще раз — смысл в том, что это написанный инструмент, который не зависит от языка, а сейчас пишут на чем удобно и крайне больно из условного монолита вытаскивать настройки безопасности, а так они на уровне базы и все просто.
> Для in-house софта можно и так, но никогда так не делал.
это понятно, для 3.5 пользователей можно так, а для более-менее приличного портала, уже накладно
> А так завожу пользователя под каждую роль, условно admin/user/guest + current_setting('user_id')::int (set role, set user_id перед запросом).
т.е. перед каждым запросом создается роль? Я не силен в PG, хочу для себя понять
> И еще раз — смысл в том, что это написанный инструмент, который не зависит от языка, а сейчас пишут на чем удобно и крайне больно из условного монолита вытаскивать настройки безопасности, а так они на уровне базы и все просто.
Ну тут зависимость все равно остается — просто это зависимость от БД. Свои плюсы и минусы.
К.т. такой подход, как по мне, немного ограничивает в возможностях, какую-то сложную логику, завязанную на условное полнолуние (как, бывает, хотят) уже не получится сделать
> это понятно, для 3.5 пользователей можно так, а для более-менее приличного портала, уже накладно
Это имеет смысл, если нет четких классических ролей (к примеру менеджеру из одного отдела разрешили смотреть данные другого). Бываю ситуации, тут на месте смотреть стоит.
> т.е. перед каждым запросом создается роль? Я не силен в PG, хочу для себя понять
У пользователя в таблице хранится поле role и перед каждым запросом пользователя делается
-- какие-то "переменные" на которые опирается логика ограничений
set role to 'my_app_user';
select set_config('app.user_id', '1000');
-- сам запрос
select * from my_posts;
> немного ограничивает в возможностях, какую-то сложную логику
Ограничивает, но достаточные сложные кейсы все равно возможно. В таком месте в последнюю очередь хочется много ограничений.
Без проблем делаются ограничения вида «этот пользователь может удалять любые коментарии в своих постах, если они не от админа».
Мы сделали на GraphQL сервисы для получения данных о гос.закупках.
Миллионы записей, высокая нагрузка, большое количество запросов на чтение/поиск данных.
GraphQL возвращает актуальную выборку данных из базы (или из кеша, если он есть, тут как напишите).
Пример проблемы с иерархической структурой, пожалуйста?
При чем тут EAV? Не путайте модель данных и инструмент для получения данных. Модель пилите как хотите, а GraphQL помогает просто удобно выдернуть из неё данные.
Вот ссылка, посмотрите схему того же GitHub-а.
Дата модель под капотом 100% простецкая, а вот GraphQL схема совсем другое дело.
graphql-voyager
Вы кажется совсем не понимаете что такое GraphQL. Он не должен дублировать Ваши дата модели.
GraphQL помогает крутить, вертеть, перемешивать дата модели, запрашивать связанные поля и смежные данные или наоборот убирать ненужные поля и всё это в одном запросе.
1. Иерархические структуры. Самый тривиальный случай для сайта. Категории продуктов и подкатегории с заранее неизвестными уровнем вложенности. Действительно получается что нельзя запросить рекурсивно. Только задавая заранее фиксированный уровень вложенности или же преобразовывать в массив. Вот Вам и GRAPHql
2. Тоже тривиальный случай для любого сайта. Характеристики товаров. Фактически объекты с зараннее неизвестной структурой. Тут соглашусь что проблем больших не будет. Т.к. свойства можно хранить в массиве с постоянной структурой как собственно eav и хранится в реляционных базах данных например name, value,unit
Тип Category
с полем Subcategory
у которого тоже тип Category
.
В резолвере поля Subcategory
берется параметр source
(родитель) и по source.id
выдергиваются из базы все подкатегории.
Засовываем это всё в Dataloader
и запрос вида:
category {
title
subcategory {
title
subcategory {
title
}
}
}
Превращается в 3 запроса в базу, который выдернет все категории с нужными id-шниками под каждый подзапрос.
Что Вы собираетесь запрашивать рекурсивно и пример из реальной жизни, где это используется?
Вот запрос сверху который выведет все категории, подкатегории и подподкатегории.
Что значит заранее неизвестный уровень вложенности. Снаружи АПИ человек всегда знает что он запрашивает. Или Вы даете апи, где можно рекурсивно все данные запросить и положить бэк сотней запросов?
То есть конкретных примеров и ответа на свой вопрос Я не получу?
К тому же никто не мешает сделать запрос вида:
Categories { id, title, parentId }
и получить всю структуру иерархии.
или же отдавать структуру так как она хранится в реляционной базе данных парами родитель-потомок— то есть ничего не мешает.
По конкретным ответам на все вопрсоы постараюсь сейчас сделать это:
Что Вы собираетесь запрашивать рекурсивно и пример из реальной жизни, где это используется?
В реальной жизни, иерархические структуры с заранее неопределенным количеством уровней встречаются повсеместно. Например:
1. Организационная структура предприятия, организации, учреждения.
2. Состав промышленного изделия
3. Декомпозиция задач
4. Граф социальных связей
5. Граф комментариев Хабра-Хабра
Что значит заранее неизвестный уровень вложенности.
Означает то что при разработке системы нет способа определить максимальное количество уровней вложенности. Например в организации на момент был максимальный уровень иерархии 7. После создания филиала — стал 8.
В текущий номенклатуры были промышленного изделия с максимальной глубиной иерархии 12. Через неделю пришла конструкторская документация с глубиной иерархии 34.
Или Вы даете апи, где можно рекурсивно все данные запросить и положить бэк сотней запросов?
Структура с уровнем вложенности например 100 (предположим это промышленное изделие) может занимать меньше памяти и проходить быстрее чем выборка данных с описаниями и характеристиками по одному товару. На фронтенде разработчик знает что структура рекурсивная и для этого использует рекурсивные компоненты для реализации интерфейса.
- Организационная структура предприятия, организации, учреждения.
- Состав промышленного изделия
- Декомпозиция задач
- Граф социальных связей
- Граф комментариев Хабра-Хабра
И в чем проблема?
Хочешь вывести всё, запрашивай ключ родитель
и строй иерархию на фронте.
Запроси данные из базы, сгруппируй на бэке и отдай иерархичный JSON на фронт.
Вы видите какое то другое решение?
И при чем тут вообще GraphQL? Проблему иерархии REST-ом решается?
Означает то что при разработке системы нет способа определить максимальное количество уровней вложенности. Например в организации на момент был максимальный уровень иерархии 7. После создания филиала — стал 8.
В текущий номенклатуры были промышленного изделия с максимальной глубиной иерархии 12. Через неделю пришла конструкторская документация с глубиной иерархии 34.
Не согласен, наоборот, при разработке системы ЗАКЛАДЫВАЕТСЯ возможность неограниченной вложенности. И с такими данными работают через поле parentId. Ну или кучей подзапросов, если нужны не все данные.
Структура с уровнем вложенности например 100 (предположим это промышленное изделие).
На фронтенде разработчик знает что структура рекурсивная и для этого использует рекурсивные компоненты для реализации интерфейса.
Вы хотите сказать на сервер улетит 100 запросов и затем 100 запросов улетит в БД? Это же бред.
Не лучше ли сделать 1 запрос на сервер, а уже на сервере или 100 запросов улетит в БД или из БД возмёт все записи и отфильтрует их.
Тут по другому никак.
И при чем тут вообще GraphQL? Проблему иерархии REST-ом решается?
Я не противопоставляю graphql REST-api. Но, к слову сказать, REST-api может отдавать произвольную в том числе и иерархическую структуру данных с произвольным количеством уровней.
Не согласен, наоборот, при разработке системы ЗАКЛАДЫВАЕТСЯ возможность неограниченной вложенности. И с такими данными работают через поле parentId. Ну или кучей подзапросов, если нужны не все данные.
Никто и не утверждает иного. Говорилось о том что graphql не может сформировать объект в явном виде с неограниченной вложенностью. Т.к. graphql возвращает вложенность согласно вложенности в запросе. То что это ограничение можно преодолеть возвращая плоскую структуру данных из которой строить дерево на клиенте не делает graphql заточенным под обработку иерархических структур данных.
Вы хотите сказать на сервер улетит 100 запросов и затем 100 запросов улетит в БД? Это же бред.
Не лучше ли сделать 1 запрос на сервер, а уже на сервере или 100 запросов улетит в БД или из БД возмёт все записи и отфильтрует их.
Тут по другому никак.
Кто говорил о ста запросах в базу данных. Сто это количество уровней иерархии. Один запрос с клиента с идентификатором рута. По этому запрос один запрос в базу данных, если база данных поддерживает функционал рекурсивных запросов (это или граф-ориентированные базы или тот же postgres). Формирует ответ который кстати можно передать резольверу. Проблема в том что резольвер все уровни иерархии, которые не пришли в запросе просто не возьмет во внимание.
Я не противопоставляю graphql REST-api.
Так GraphQL это технология для замены REST-а. Это не фреймворк какой то, это не база данных, это удобный инструмент получения данных на фронте с бека.
Но, к слову сказать, REST-api может отдавать произвольную в том числе и иерархическую структуру данных с произвольным количеством уровней.
Так и GraphQL позволяет. Только если это произвольная вложенность, то тут статической типизации не сделаешь и по факту будет отдаваться скалярный тип JSON.
Говорилось о том что graphql не может сформировать объект в явном виде с неограниченной вложенностью. Т.к. graphql возвращает вложенность согласно вложенности в запросе.
И снова, можно вернуть JSON со сколь угодно глубокой вложенностью, только теряется одна из основных фишек — строгая статическая типизация ответа.
Вот пример, если Вы хотите получить какой то объект, не типизированный, с бесконечной вложенностью, без parentId.
Articles { Title String, Body String, Comments JSON }
Формирует ответ который кстати можно передать резольверу.
Делаем из него JSON и возвращаем клиенту.
Вот только нафига это надо.
Хотите на фронте построить какую то иерархию, делайте запрос с получением parentId и выстраиваете иерархию.
Например файловые менеджеры на фронте так и делают, только вместо parent-а у них путь.
Сложная менюшка или примеры приведенные выше, всё делается также.
Добавить кастомные поля к объекту ошибки не получится
кидать GraphQLError не пробовали? в сочетании с formatError вполне работает. пакет – самый обычный ванильный graphql из npm.
у конструктора GraphQLError используется параметр extensions. в одном проекте используем такую обёртку:
class GqlError extends GraphQLError {
constructor(params) {
if (!params) {
throw new Error('Cant construct GqlError without params')
}
const { message, code, context = {} } = params
super(message, null, null, null, null, null, { code, context, errorId: uuid() })
}
}
Остальные классы ошибок наследуются от этого, и там уже конструкторы принимают свои параметры и заворачивают всё как нужно в контекст… вроде до клиента всё долетает…
graphql — подводные камни