Чтобы выполнить операцию, подобную соединению, мы можем использовать как GraphQL, так и Mongoose для достижения этой цели.

Прежде чем задать какой-либо вопрос, я хотел бы привести следующий пример Task / Activities (этот код не тестируется, он приведен только для примера):

Task {
  _id,
  title,
  description,
  activities: [{ //Of Activity Type
    _id,
    title
  }]
}

В mongoose мы можем получить действия, связанные с задачей, с помощью метода заполнить, например это:

const task = await TaskModel.findbyId(taskId).populate('activities');

Используя GraphQL и Dataloader, мы можем получить тот же результат примерно так:

const DataLoader = require('dataloader');
const getActivitiesByTask = (taskId) => await ActivityModel.find({task: taskId});
const dataloaders = () => ({
    activitiesByTask: new DataLoader(getActivitiesByTask),
});
// ...
// SET The dataloader in the context
// ...

//------------------------------------------
// In another file
const resolvers = {
    Query: {
        Task: (_, { id }) => await TaskModel.findbyId(id),
    },
    Task: {
        activities: (task, _, context) => context.dataloaders.activitiesByTask.load(task._id),
    },
};

Я попытался посмотреть, есть ли статья, демонстрирующая, какой способ лучше с точки зрения производительности, исчерпания ресурсов и т. Д., Но мне не удалось найти какое-либо сравнение этих двух методов.

Любая информация будет полезна, спасибо!

5
Strider 5 Окт 2018 в 17:44

1 ответ

Лучший ответ

Важно отметить, что загрузчики данных - это не просто интерфейс для ваших моделей данных. Хотя загрузчики данных преподносятся как «упрощенный и согласованный API для различных удаленных источников данных», их основное преимущество в сочетании с GraphQL заключается в возможности реализовать кэширование и пакетирование в контексте одного запроса. Такая функциональность важна в API, которые имеют дело с потенциально избыточными данными (подумайте о запросах пользователей и друзей каждого пользователя - существует огромная вероятность повторного получения одного и того же пользователя несколько раз).

С другой стороны, метод мангуста populate на самом деле является просто способом агрегирования нескольких запросов MongoDB. В этом смысле сравнивать их все равно, что сравнивать яблоки и апельсины.

Более справедливым сравнением может быть использование populate, как показано в вашем вопросе, а не добавление преобразователя для activities в следующих строках:

activities: (task, _, context) => Activity.find().where('id').in(task.activities)

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

Если запрашивается поле activities, оба подхода будут выполнять одинаковое количество циклов обмена между сервером и базой данных - разница в производительности, вероятно, будет незначительной. Однако ваш запрос может вообще не включать поле activities. В этом случае преобразователь activities никогда не будет вызван, и мы можем сохранить один или несколько запросов к базе данных, создав отдельный преобразователь activities и выполняя там всю работу.

По теме…

Насколько я понимаю, агрегирование запросов в MongoDB с использованием чего-то вроде $lookup обычно менее эффективно, чем простое использование populate (некоторые разговоры по этому поводу можно найти на здесь). Однако в контексте реляционных баз данных при рассмотрении вышеупомянутых подходов следует задуматься над дополнительными соображениями. Это потому, что ваша первоначальная выборка в родительском преобразователе может быть выполнена с использованием объединений, что обычно будет намного быстрее, чем выполнение отдельных запросов к базе данных. Это означает, что за счет замедления запросов без полей вы можете сделать другие запросы значительно быстрее.

7
Daniel Rearden 5 Окт 2018 в 16:37