我使用MongoDB作为数据库和GraphQL 我在模型上使用猫鼬 我意识到我的GraphQL查询速度很慢,因为一次又一次地加载相同的文档。 我想使用DataLoader解决我的问题,但是我不知道如何。

假设我有以下架构,描述了与朋友一起的用户:

// 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()
  }
}

这是我数据库中的一些示例数据:

[
  { 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] },
]

当我执行以下GraphQL查询时:

{
  users {
    name
    friends {
      name
    }
  }
}

每个用户加载多次。 我希望每个用户Mongoose文档仅加载一次。

什么不起作用

定义“全局”数据加载器以获取朋友

如果我将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))
})

那么我的用户将被永久缓存,而不是基于每个请求。

在解析器中定义数据加载器

这似乎更合理:每个请求一种缓存机制。

// 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)
    }
  }
}

但是,现在在Mongoose模式的friends方法中undefined userLoader 然后,让我们在解析器中移动模式!

// 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)
    }
  }
}

嗯...现在,Mongoose在第二个请求上抱怨:解析器再次被调用,Mongoose不喜欢使用相同的模型名称定义两个模型。

“虚拟填充”功能没有用,因为我什至不能告诉Mongoose通过数据加载器而不是直接通过数据库获取模型。

有人遇到过同样的问题吗? 有没有人建议如何结合使用Mongoose和Dataloader? 谢谢。

注意:我知道因为我的架构是“关系的”,所以我应该使用关系数据库而不是MongoDB。 我不是那种选择的人。 在我们可以迁移之前,我必须忍受它。

#1楼 票数:4

将您的猫鼬模式保存在单独的模块中。 您不想在每个请求时都创建架构,而只是在第一次导入模块时。

const userSchema = new Schema({
  name: String,
  friendIds: [String]
})
const User = mongoose.model("User", userSchema)

module.exports = { User }

如果需要,您还可以导出在同一模块中创建加载程序的函数。 但是请注意,我们不想导出一个加载程序的实例,而只是要导出一个实例的函数。

// ...
const getUserLoader = () => new DataLoader((userIds) => {
  return User.find({ _id: { $in: userIds } }).execute()
})
module.exports = { User, getUserLoader }

接下来,我们要在上下文中包含我们的加载器。 具体执行方式将取决于您使用哪个库来实际显示graphql端点。 例如,在apollo-server ,上下文作为配置的一部分传入。

new ApolloServer({
    typeDefs,
    resolvers,
    context: ({ req }) => ({
        userLoader: getUserLoader()
    }),
})

这将确保我们为每个请求创建一个新的加载器实例。 现在,您的解析器可以像这样调用加载程序:

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)
    },
  },
}

  ask by Aymeric Bouzy aybbyk translate from so

未解决问题?本站智能推荐:

1回复

如何在Mongoose中使用DataLoader

我正在尝试与Mongoose一起构建以下DataLoader用例: 加载多个PurchaseOrders时,我面临以下问题: 在DataLoader的ids参数中多次调用一个customer_id。 因此,在连续调用中,对我的加载程序的几个请求都调用了一个示例ID 5cee853ea
1回复

如何在本地将expressq的graphql和mongoose的mongodb一起使用来检索文档?

这是我的server.js文件: 这是我的schema.js文件: 我无法获取任何数据,在我的graphiql查询中总是返回null : 结果: 在mongo shell中,db.members.findOne显示: 因此该文档存在于db的集合中。 我如何让graphql与
1回复

Mongoose和GraphQL查询

这里我有一个猫鼬模式 我如何在 GraphQL 输入上做到这一点? 所以我可以将它们保存到 mongodb 喜欢
1回复

同时利用GraphQL和Mongoose

可以同时使用GraphQL和Mongoose吗? 到目前为止,我已经能够集成GraphQL和Mongoose来处理填充数据库,但是我正在努力了解它如何才能检索数据,特别是具有嵌套引用的数据。 考虑以下模式: Bar模式本质上是相同的,只是一个用于“名称”的字段。 是否可以运行Gr
3回复

跨mongoose和GraphQL的ObjectId字段使用的正确类型是什么?

在本教程之后 ,我有一个猫鼬模型:(我使用术语“帐户”而不是“Todo”,但它是一样的) 和GraphQLObjectType: 和GraphQL突变创建其中之一: 运行查询后: 在GraphiQL中,我得到了响应: 对象的创建是成功的,我可以在MongoDB Compass
2回复

使用Mongoose,node和graphql防止控制台错误

我正在使用Express,Graphql(Apollo服务器)和mongoose制作API服务器。 我正在测试用户创建。 当电子邮件重复时,mongoose会抛出错误(验证错误.Bustal = true)并且graphql处理非常好。 但控制台(终端)也显示错误。 我该如何预防这个问
1回复

使用graphql-compose-mongoose将对象添加到mongodb模型中的对象数组

我制作了一个 GraphQL API 来检索和操作 mongo 数据库中的数据。 此数据表示包含 Author.ts 中所示书籍列表的作者。 作者.ts:import { Document, Model, model, Schema } from "mongoose";import { Ibook
1回复

使用Mongoose和GraphQL从填充模型有条件地返回值的最有效方法?

我的目标是根据填充后从模型本身检索的数据有条件地返回模型值。 这是我目前的解决方案: 实用程序.js: 我目前正在遍历 User 中的每个数组,我认为这远不是实现这一目标的最有效方法。 有没有人有比这更好的解决方案?