Mongoose populate позволяет вам заменять указанные пути в документе документами из других коллекций. Это хорошо известно и хорошо задокументировано. Если вы не знакомы с этим, я рекомендую вам прочитать следующее, прежде чем продолжить https://mongoosejs.com/docs/populate.html.

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

Независимо от того, являетесь ли вы внутренним разработчиком или разработчиком полного стека, вы должны подумать об интеграции с клиентским кодом. Если вы используете мангуст, скорее всего, вы работаете со стеком JS.

Сегодня большинство интерфейсных фреймворков JS хорошо структурированы и позволяют печатать. Данные, которые отправляет ваш API, должны быть ясными, а именно:

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

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

В документации вы можете увидеть пример с моделями Story и Person. Это хороший пример того, как работает заполнение. На мой взгляд, это не лучший вариант для демонстрации того, как вы должны его использовать. Давайте вместе разберемся.

Для нашего примера мы упростим его:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const personSchema = Schema({
    _id: Schema.Types.ObjectId,
    name: String,
    age: Number,
});
const storySchema = Schema({
    author: { type: Schema.Types.ObjectId, ref: 'Person' },
    title: String,
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);

Если вы это сделаете, Story.findOne() вернет что-то вроде:

{
    _id: 'some-id',
    author: 'some-other-id',
    title: 'some-title'
}

а Story.findOne().populate('author') вернет что-то вроде:

{
    _id: 'some-id',
    author: { _id: 'some-other-id', name: 'some-name', age: 21 },
    title: 'some-title'
}

Как видите, в поле «автор» хранится ObjectId. Когда вы его заполните, он будет содержать Person. Это несовместимо, поэтому мы хотим избежать этого, особенно если мы планируем отправлять эти данные через API.

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

  • иногда возвращаются бесполезные данные
  • дополнительные запросы, которых можно было избежать

Вы также можете ввести данные во внешнем коде как String или Person. Но это будет означать более защитный код и повышенную сложность.

Сначала это может быть нормально. Но когда ваше приложение разрастется и количество использований увеличивается, отслеживать его станет труднее. Новым разработчикам потребуется больше времени, чтобы понять код. Это важно, даже если вы знаете, что участвуете в проекте один. Если вы какое-то время не работаете над какой-либо функцией, вам тоже понадобится больше времени, чтобы вспомнить, как она работает.

Это будет проблемой, особенно с недавними интерфейсными технологиями, такими как Flutter, потому что вы должны вводить все данные.

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

Используйте поле для хранения идентификатора и виртуальное поле для загрузки документа (ов).

Давайте применим это к нашему предыдущему примеру:

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const personSchema = Schema({
    _id: Schema.Types.ObjectId,
    name: String,
    age: Number,
});
const storySchema = Schema({
    author_id: { type: Schema.Types.ObjectId, ref: 'Person' },
    title: String,
});
storySchema.virtual('author', {
    justOne: true,
    localField: 'author_id',
    foreignField: '_id',
    ref: 'Person'
});
const Story = mongoose.model('Story', storySchema);
const Person = mongoose.model('Person', personSchema);

Во внешнем коде вы можете ввести author_id как строку или ObjectId, а author как Person.

Таким образом, код остается чистым и непротиворечивым.

Он также будет более гибким для дальнейшего улучшения. Виртуальное заполнение позволяет вам определять более сложные отношения, вы можете добавлять виртуальные «истории», «last_story»… в схему Person без каких-либо изменений в базе данных, то есть без миграций.

Сообщите мне, что вы думаете об этой статье!