Является ли круговая ссылка схемы GraphQL анти-паттерном? - PullRequest
0 голосов
/ 20 декабря 2018
Схема

graphql выглядит следующим образом:

type User {
  id: ID!
  location: Location
}

type Location {
  id: ID!
  user: User
}

Теперь клиент отправляет запрос graphql.Теоретически, User и Location могут циклически ссылаться друг на друга бесконечно.

Я думаю, что это анти-паттерн.Насколько мне известно, не существует промежуточного программного обеспечения или способа ограничения глубины вложенности запроса как в сообществе graphql, так и apollo.

Этот бесконечный запрос глубины вложенности будет стоить больших ресурсов для моей системы, напримерпропускная способность, оборудование, производительность.Не только на стороне сервера, но и на стороне клиента.

Итак, если схема graphql допускает циклическую ссылку, должны быть некоторые промежуточные программы или способы ограничения глубины вложенности запроса.Или добавьте некоторые ограничения для запроса.

Может быть, лучше не использовать циклическую ссылку?

Я предпочитаю отправлять другой запрос и выполнять несколько операций в одном запросе.Это гораздо проще.

Обновление

Я нашел эту библиотеку: https://github.com/slicknode/graphql-query-complexity. Если graphql не ограничивает циклическую ссылку.Эта библиотека может защитить ваше приложение от истощения ресурсов и DoS-атак.

Ответы [ 2 ]

0 голосов
/ 24 декабря 2018

Это зависит.

Полезно помнить, что одно и то же решение может быть хорошим шаблоном в некоторых контекстах и ​​антипаттерном в других.Ценность решения зависит от контекста, который вы используете.- Мартин Фаулер

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

С другой стороны, циклические ссылки обеспечивают дополнительный уровень гибкости.Работая с вашим примером, если мы примем следующую схему:

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 также кэширует загруженные записи.Это означает, что последующие загрузки для той же записи (внутри одного и того же запроса) не попадают в базу данных, а вместо этого извлекаются из памяти.

0 голосов
/ 20 декабря 2018

Шаблон, который вы показываете, является вполне естественным для «графика», и я не думаю, что он особенно обескуражен в GraphQL. GitHub GraphQL API - это то, на что я часто обращаю внимание, когда я задаюсь вопросом «как люди создают большие API-интерфейсы GraphQL», и там обычно существуют циклы объектов: Репозиторий имеет RepositoryOwner , который может быть Пользователь , который имеет список repositories.

Как минимум graphql-ruby имеет элемент управления для ограничения глубины вложения .Apollo, очевидно, не имеет такого элемента управления, но вы можете создать пользовательский источник данных или использовать библиотеку DataLoader , чтобы избежать повторной выборки уже имеющихся у вас объектов.

...