GraphQL сулит огромные преимущества. Вот некоторые из них:
Но с точки зрения бэкенда одно из главнейших преимуществ заключается в следующем: обработка данных бэкенд-сервером уменьшается на основе запроса от клиента.
Обратите внимание: 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”
Комментарии