Это зависит.
Полезно помнить, что одно и то же решение может быть хорошим шаблоном в некоторых контекстах и антипаттерном в других.Ценность решения зависит от контекста, который вы используете.- Мартин Фаулер
Допустимо, что круговые ссылки могут создавать дополнительные проблемы.Как вы указали, они представляют потенциальную угрозу безопасности, поскольку позволяют злоумышленнику создавать потенциально очень дорогие запросы.По моему опыту, они также облегчают непреднамеренное получение данных командами клиентов.
С другой стороны, циклические ссылки обеспечивают дополнительный уровень гибкости.Работая с вашим примером, если мы примем следующую схему:
type Query {
user(id: ID): User
location(id: ID): Location
}
type User {
id: ID!
location: Location
}
type Location {
id: ID!
user: User
}
ясно, что мы могли бы потенциально сделать два разных запроса для эффективной выборки одних и тех же данных:
{
# query 1
user(id: ID) {
id
location {
id
}
}
# query 2
location(id: ID) {
id
user {
id
}
}
}
Если основные потребителивашего API - одна или несколько клиентских команд, работающих над одним проектом, это может не иметь большого значения.Вашему внешнему интерфейсу нужны данные, которые он выбирает, чтобы иметь определенную форму, и вы можете разработать свою схему в соответствии с этими потребностями.Если клиент всегда выбирает пользователя, может получить местоположение таким образом и не нуждается в информации о местоположении вне этого контекста, имеет смысл иметь только запрос user
и пропустить поле user
из типа Location
,Даже если вам нужен запрос location
, возможно, не имеет смысла выставлять в нем поле user
, в зависимости от потребностей вашего клиента.
С другой стороны, представьте, что ваш API используетсябольшее количество клиентов.Возможно, вы поддерживаете несколько платформ или несколько приложений, которые выполняют разные функции, но используют один и тот же API для доступа к вашему уровню данных.Или, может быть, вы выставляете открытый API, предназначенный для интеграции сторонних приложений с вашим сервисом или продуктом.В этих сценариях ваше представление о том, что нужно клиенту, значительно размыто.Внезапно, более важно раскрыть широкий спектр способов запроса базовых данных, чтобы удовлетворить потребности как текущих клиентов, так и будущих.То же самое можно сказать и об API для одного клиента, чьи потребности могут со временем развиваться.
Всегда можно «сплющить» вашу схему, как вы предлагаете, и предоставить дополнительные запросы в отличие от реализации реляционных полей.Однако, будет ли это «проще» для клиента, зависит от клиента.Наилучшим подходом может быть предоставление каждому клиенту возможности выбрать структуру данных, соответствующую его потребностям.
Как и в большинстве архитектурных решений, существует компромисс, и подходящее решение для вас может не совпадать с решением для другогоteam.
Если у вас do есть круговые ссылки, все надежды не потеряны .Некоторые реализации имеют встроенные элементы управления для ограничения глубины запроса.GraphQL.js этого не делает, но есть библиотеки вроде graphql-deep-limit , которые делают именно это.Стоит отметить, что ширина может быть такой же большой проблемой, как глубина - независимо от того, есть ли у вас циклические ссылки, вы должны изучить реализацию нумерации страниц с максимальнымограничивать при разрешении списков, чтобы клиенты не могли одновременно запрашивать тысячи записей.
Как указывает @DavidMaze, помимо ограничения глубины клиентских запросов, вы также можете использовать dataloader
для уменьшениястоимость многократного извлечения одной и той же записи из слоя данных.Хотя dataloader
обычно используется для пакетных запросов, чтобы обойти «проблему n + 1», которая возникает из-за ленивой загрузки ассоциаций, это также может помочь здесь.В дополнение к пакетной обработке, dataloader также кэширует загруженные записи.Это означает, что последующие загрузки для той же записи (внутри одного и того же запроса) не попадают в базу данных, а вместо этого извлекаются из памяти.