Решаем проблему запроса N+1 в GraphQL с помощью Dataloader



Книга Решаем проблему запроса N+1 в GraphQL с помощью Dataloader

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


В предыдущей статье мы закончили на том, что при разрешении полей возникает проблема запроса N+1.


Теперь мы рассмотрим, как решить указанную проблему с помощью Dataloader.


Что такое Dataloader?


Dataloader  —  это библиотека, которая пакует последовательные запросы и “под капотом” составляет один запрос данных. Этот запрос может быть сделан к любому источнику данных, например к базе данных или веб-сервису.


Загрузчик данных принимает в качестве аргумента массив, обрабатывает данные с помощью этого аргумента и возвращает массив объектов.


Элемент с N-ым индексом возвращаемого массива будет рассматриваться DataLoader’ом как данные для N-го элемента во входном аргументе.


Давайте теперь реализуем postsLoader.


const DataLoader = require('dataloader');

const postsLoader = new DataLoader( async (userIds) => {
// Assume, userIds = [ 1, 2 ]

let posts = await Post.findAll( { where: { userId: userIds } } );
// posts = [ {title: "A", userId: 1}, {title: "B", userId: 1}, {title: "C", userId: 2} ]

let postsGroupedByUser = userIds.map ( userId => {
return posts.filter( post => post.userId == userId );
});

// postsGroupedByUser = [
// [
// {title: "A", userId: 1},
// {title: "B", userId: 1}
// ],
// [
// {title: "C", userId: 2}
// ]
// ]

return postsGroupedByUser;
})

Теперь мы будем использовать этот postsLoader для разрешения сообщений posts. Обновленные resolvers будут выглядеть следующим образом:


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

Когда клиент запрашивает пользователей вместе с полями сообщений, то для каждого пользователя, разрешенного в запросе “пользователи” (users), этот распознаватель “сообщений” (posts) будет вызван с родительским аргументом, равным объекту пользователя. Используя этого пользователя, мы можем найти сообщения.


Как видно из распознавателя поля posts, мы все еще запрашиваем одно сообщение с помощью API load загрузчика данных.


Несколько вызовов postsLoader.load() будут упакованы в пакет, а затем только один раз вызовется postsLoader .


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


Так гарантируется, что мы только однократно попадем в базу данных, чтобы получить данные и, следовательно, проблема запроса N+1 будет решена.

Предостережение при реализации Dataloader


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


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


Следовательно загрузчик данных всегда должен быть определен для каждого конкретного запроса.


Таким образом, разные экземпляры Dataloader будут использоваться разными запросами. Лучше всего определить их в контексте запроса GraphQL.


Новая реализация загрузчика данных будет выглядеть следующим образом:


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

const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({req}) => {
return {
postsLoader: postsLoader
}
}
});

Надеюсь, статья была для вас полезной. В репозитории можно найти разобранную выше реализации решателя users с помощью сервера Apollo GraphQL.




960   0  

Comments

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