Почему нельзя разрешать поля GraphQL как конечные точки REST


GraphQL сулит огромные преимущества. Вот некоторые из них:

  • Один и тот же API может использоваться несколькими клиентами, поскольку есть возможность запрашивать необходимые данные из API по условию.
  • Сокращается использование данных клиентом, так как с сервера передаются только необходимые данные.
  • Бэкенд API не имеет настолько тесной связи с местом своего применения.
  • Но с точки зрения бэкенда одно из главнейших преимуществ заключается в следующем: обработка данных бэкенд-сервером уменьшается на основе запроса от клиента.

    Обратите внимание: GraphQL не предполагает этого преимущества по умолчанию. Если вы не запрограммируете всё правильно, то нагрузка на бэкенд-сервер всегда будет одинаковой независимо от запроса клиента.

    То есть не нужно писать код точно таким же образом, как вы писали бы конечную точку REST.

    Сценарий

    Рассмотрим пример, где будут фигурировать пользователи и сообщения от них. Ниже приведены структуры двух таблиц:

    Таблица пользователей Таблица сообщений

    Допустим, чтобы соотнести пользователей в списке с их сообщениями, мне нужен распознаватель. Как бы я написал этот распознаватель?

    Примечание: для решения этой задачи я буду использовать NodeJS с Apollo GraphQL server и Sequelize.

    Одно из возможных решений

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

    const typeDefs = gql` type PostType { title: String description: String } type UserType { id: Int name: String email: String posts: [PostType] } type Query { users: [UserType] } `;

    А распознаватель пользователей будет таким:

    const resolvers = { Query: { users: async () => { try { let users = await User.findAll() let userIds = users.map( user => user.id) let posts = await Post.findAll({where: {userId: userIds}}); let usersData = users.map( user => { let userPosts = posts.filter( post => post.userId == user.id); return { id: user.id, name: user.name, email: user.email, posts: userPosts.map ( post => { return { title: post.title, description: post.description } }) } }) return usersData } catch (e) { console.log(e) } }, }, };

    Безусловно, проблему это решает. Но эффективно ли решение?

    Давайте взглянем на логи для двух случаев.

    Примечание: на левой стороне скриншотов ниже  —  выполненный запрос и его результат. На правой стороне  —  логи приложения, заполняемые при выполнении запроса GraphQL.

    Случай первый  —  запрос пользователей с данными сообщений

    Случай второй  —  запрос пользователей без данных сообщений

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

    Более удачное решение

    GraphQL позволяет распознавать каждое поле внутри другого поля. Вот что нам для этого нужно:

    const resolvers = { UserType: { posts: async (parent) => { return await Post.findAll({where: {userId: parent.id}}); } }, Query: { users: async () => { try { return await User.findAll() } catch (e) { console.log(e) } }, }, };

    Мы здесь распознаем поле posts, принадлежащее UserType (именно этот тип возвращает распознаватель users). Таким образом, сервер GraphQL теперь будет распознавать это поле только тогда, когда поступает явный запрос от клиента.

    Случай первый  —  запрос пользователей с данными сообщений

    Случай второй  —  запрос пользователей без данных сообщений

    И, как это очевидно из логов, когда в запросе отсутствует post field, запрос на извлечение сообщений posts не выполняется.

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

    Надеюсь, этот материал вам пригодится!


    Перевод статьи Shriram Balakrishnan, “Do not resolve your GraphQL fields like a REST endpoint”


    Поделиться статьей:


    Вернуться к статьям

    Комментарии

      Ничего не найдено.