Как использовать Mongoose с GraphQL и DataLoader? - PullRequest
0 голосов
/ 12 октября 2018

Я использую MongoDB в качестве базы данных и GraphQL .Я использую Mongoose для моей модели.Я понял, что мои запросы к 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))
})

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

Определение загрузчика данных в распознавателе

Thisкажется более разумным: один механизм кэширования на запрос.

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

Однако userLoader теперь undefined в методе friends в схеме Mongoose.Давайте теперь переместим схему в резольвере!

// 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 не нравится, когда определяются две модели с одной и той же моделью.name.

Функция «виртуального заполнения» бесполезна, потому что я даже не могу сказать Mongoose, чтобы она выбирала модели через загрузчик данных, а не через базу данных напрямую.

Вопрос

У кого-нибудь была такая же проблема?У кого-нибудь есть предложения по использованию Mongoose и Dataloader в комбинации?Спасибо.

Примечание: я знаю, поскольку моя схема "реляционная", я должен использовать реляционную базу данных, а не MongoDB.Я был не один, чтобы сделать этот выбор.Я должен жить с этим, пока мы не сможем мигрировать.

1 Ответ

0 голосов
/ 13 октября 2018

Храните вашу схему мангуста в отдельном модуле.Вы не хотите создавать свою схему каждый запрос - просто при первом импорте модуля.

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)
    },
  },
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...