[英]How to use Mongoose with GraphQL and DataLoader?
I am using MongoDB as my database and GraphQL . 我使用MongoDB作为数据库和GraphQL 。 I am using Mongoose for my model.
我在模型上使用猫鼬 。 I realised my GraphQL queries are slow because the same documents are being loaded over and over again.
我意识到我的GraphQL查询速度很慢,因为一次又一次地加载相同的文档。 I would like to use DataLoader to solve my problem, but I don't know how.
我想使用DataLoader解决我的问题,但是我不知道如何。
Let's say I have the following schema, describing users with friends : 假设我有以下架构,描述了与朋友一起的用户:
// mongoose schema
const userSchema = new Schema({
name: String,
friendIds: [String],
})
userSchema.methods.friends = function() {
return User.where("_id").in(this.friendIds)
}
const User = mongoose.model("User", userSchema)
// GraphQL schema
const graphqlSchema = `
type User {
id: ID!
name: String
friends: [User]
}
type Query {
users: [User]
}
`
// GraphQL resolver
const resolver = {
Query: {
users: () => User.find()
}
}
Here is some example data in my database : 这是我数据库中的一些示例数据:
[
{ id: 1, name: "Alice", friendIds: [2, 3] },
{ id: 2, name: "Bob", friendIds: [1, 3] },
{ id: 3, name: "Charlie", friendIds: [2, 4, 5] },
{ id: 4, name: "David", friendIds: [1, 5] },
{ id: 5, name: "Ethan", friendIds: [1, 4, 2] },
]
When I do the following GraphQL query : 当我执行以下GraphQL查询时:
{
users {
name
friends {
name
}
}
}
each user is loaded many times. 每个用户加载多次。 I would like each user Mongoose document to be loaded only once.
我希望每个用户Mongoose文档仅加载一次。
If I change the friends
method to : 如果我将
friends
方法更改为:
// mongoose schema
const userSchema = new Schema({
name: String,
friendIds: [String]
})
userSchema.methods.friends = function() {
return userLoader.load(this.friendIds)
}
const User = mongoose.model("User", userSchema)
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
then my users are cached forever rather than on a per request basis. 那么我的用户将被永久缓存,而不是基于每个请求。
This seems more reasonable : one caching mechanism per request. 这似乎更合理:每个请求一种缓存机制。
// GraphQL resolver
const resolver = {
Query: {
users: async () => {
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
const userIds = await User.find().distinct("_id")
return userLoader.load(userIds)
}
}
}
However, userLoader
is now undefined
in the friends
method in Mongoose schema. 但是,现在在Mongoose模式的
friends
方法中undefined
userLoader
。 Let's move the schema in the resolver then! 然后,让我们在解析器中移动模式!
// GraphQL resolver
const resolver = {
Query: {
users: async () => {
const userLoader = new Dataloader(userIds => {
const users = await User.where("_id").in(userIds)
const usersMap = new Map(users.map(user => [user.id, user]))
return userIds.map(userId => usersMap.get(userId))
})
const userSchema = new Schema({
name: String,
friendIds: [String]
})
userSchema.methods.friends = function() {
return userLoader.load(this.friendIds)
}
const User = mongoose.model("User", userSchema)
const userIds = await User.find().distinct("_id")
return userLoader.load(userIds)
}
}
}
Mh ... Now Mongoose is complaining on the second request : resolver gets called again, and Mongoose doesn't like 2 models being defined with the same model name. 嗯...现在,Mongoose在第二个请求上抱怨:解析器再次被调用,Mongoose不喜欢使用相同的模型名称定义两个模型。
"Virtual populate" feature are of no use, because I can't even tell Mongoose to fetch models through the dataloader rather than through the database directly. “虚拟填充”功能没有用,因为我什至不能告诉Mongoose通过数据加载器而不是直接通过数据库获取模型。
Has anyone had the same problem? 有人遇到过同样的问题吗? Does anyone have a suggestion on how to use Mongoose and Dataloader in combination?
有没有人建议如何结合使用Mongoose和Dataloader? Thanks.
谢谢。
Note: I know since my schema is "relational", I should be using a relational database rather than MongoDB. 注意:我知道因为我的架构是“关系的”,所以我应该使用关系数据库而不是MongoDB。 I was not the one to make that choice.
我不是那种选择的人。 I have to live with it until we can migrate.
在我们可以迁移之前,我必须忍受它。
Keep your mongoose schema in a separate module. 将您的猫鼬模式保存在单独的模块中。 You don't want to create your schema each request -- just the first time the module is imported.
您不想在每个请求时都创建架构,而只是在第一次导入模块时。
const userSchema = new Schema({
name: String,
friendIds: [String]
})
const User = mongoose.model("User", userSchema)
module.exports = { User }
If you want, you can also export a function that creates your loader in the same module. 如果需要,您还可以导出在同一模块中创建加载程序的函数。 Note, however, that we do not want to export an instance of a loader, just a function that will return one.
但是请注意,我们不想导出一个加载程序的实例,而只是要导出一个实例的函数。
// ...
const getUserLoader = () => new DataLoader((userIds) => {
return User.find({ _id: { $in: userIds } }).execute()
})
module.exports = { User, getUserLoader }
Next, we want to include our loader in the context. 接下来,我们要在上下文中包含我们的加载器。 How exactly this is done will depend on what library you're using to actually expose your graphql endpoint.
具体执行方式将取决于您使用哪个库来实际显示graphql端点。 In
apollo-server
, for example, context is passed in as part of your configuration. 例如,在
apollo-server
,上下文作为配置的一部分传入。
new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => ({
userLoader: getUserLoader()
}),
})
This will ensure that we have a fresh instance of the loader created for each request. 这将确保我们为每个请求创建一个新的加载器实例。 Now, your resolvers can just call the loader like this:
现在,您的解析器可以像这样调用加载程序:
const resolvers = {
Query: {
users: async (root, args, { userLoader }) => {
// Our loader can't get all users, so let's use the model directly here
const allUsers = await User.find({})
// then tell the loader about the users we found
for (const user of allUsers) {
userLoader.prime(user.id, user);
}
// and finally return the result
return allUsers
}
},
User: {
friends: async (user, args, { userLoader }) => {
return userLoader.loadMany(user.friendIds)
},
},
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.