Пытаясь бороться с недостатками отношений в MongoDB, вот что я пытаюсь иметь в своей базе данных:
Модели:
uid, но _id также доступен.Характеристики:
Каждый пользователь может быть членом команды или независимыми пользователями приложения. Обычные пользователи могут иметь свои собственные частные проекты и активы.
Член команды может иметь доступ к активам и проектам, к которым владелец группы решил, что у них есть доступ.
Участник может создавать свои собственные частные активы и проекты.
В зависимости от роли участника, он также может делиться активами с членами своей команды.
Участник, в зависимости от своих ролей, может добавлять, удалять, редактировать активы команды, удалять или переименовывать команду, добавлять или удалять участников из команды.
Участник может просматривать список ресурсов и проектов, которыми с ним поделились в команде.
Участник может просматривать страницу отдельных активов, где он может добавлять элементы удаления в актив в зависимости от своей роли.
Участник может просматривать, редактировать, добавлять и удалять отдельные проекты в зависимости от своих ролей.
Текущая схема:
const teamSchema = new Schema({
uid: {
type: String,
required: true
},
name: {
type: String,
required: true
},
members: {
type: [String]
},
brands: {
type: [String]
}
}, { collection: 'team', timestamps: true });
const memberSchema = new Schema({
uid: {
type: String,
required: true
},
owner: {
type: String,
required: true
},
teamID: {
type: Schema.Types.ObjectId,
required: true
},
acl: {
type: String,
required: true,
enum: ['view', 'copy', 'edit', 'admin', 'owner'],
default: 'view'
},
pending: {
type: Boolean,
default: true
}
}, { collection: 'member', timestamps: true, _id: false });
const assetsSchema = new Schema({
uid: {
type: String,
required: true
},
title: {
type: String,
required: true
},
fonts: [AssetFile],
colors: [Color],
logos: [AssetFile],
images: [AssetFile],
projects: Array,
items: Array,
members: [String]
}, { collection: 'branding', timestamps: true });
const userSchema = new Schema({
uid: {
type: String,
required: true
},
email: {
type: String,
required: true,
lowercase: true,
index: { unique: true },
validate: v => validator.isEmail(v)
},
avatar: String,
files: [FilesSchema],
isAdmin: Boolean,
projectCount: {
type: Number,
default: 0
},
userName: {
type: String,
trim: true
},
userType: {
type: String,
required: true,
default: 'Free User'
},
lastSeenAt: {
type: Date
},
}, { collection: 'user', timestamps: true });
const projectSchema = new Schema({
uid: {
type: String,
required: true
},
objects: Array,
projectTitle: {
type: String,
trim: true,
default: 'Untitled Project'
},
preview: {
type: String
},
folder: {
type: String,
trim: true,
default: null
},
archived: {
type: Boolean,
default: false
},
}, { collection: 'project', timestamps: true });
Этого можно легко достичь в реляционных базах данных, но, поскольку мое приложение уже находится в открытом доступе с более чем 40 000 пользователей, переход на другую БД — непростая задача.
Можно ли этого достичь в MongoDB и как, или мне следует прекратить попытки и перейти на другую БД?
Я уже использую mongoose, но я также открыт для использования собственных кодов MongoDB (таких как агрегат, $lookup и т. д.)
Обновлено:
Для меня это больше вопрос масштабируемости, чем обучение написанию запросов в MongoDB, поэтому вот запросы, которые у меня уже есть:
Список активов:
const personalAssets = await AssetsModel.aggregate([
{ $match: { uid: user.uid } },
{ $project: { _id: 1, uid: 1, title: 1, logos: 1, teamID: 1, acl: 'owner', createdAt: 1, fonts: 1 } },
{ $sort: { createdAt: -1 } }
]);
const sharedAssets = await MemberModel.aggregate([
{
$match: {
$or: [
{ uid: user.uid },
{ owner: user._id }
]
}
},
{ $project: { _id: 0, acl: 1, teamID: 1, owner: 1 } },
{
$lookup: {
from: 'team',
let: { team_id: '$teamID' },
pipeline: [
{
$match: {
$expr: {
$eq: [ '$_id', '$$team_id' ]
}
}
},
{ $project: { _id: 0, assets: 1, teamID: '$$team_id' } }
],
as: 'team'
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ '$team', 0 ] }, '$$ROOT' ] } } },
{ $project: { assets: 1, acl: 1, teamID: 1, owner: 1 } },
{ $match: { 'assets.0': { $exists: true } } },
{
$lookup: {
from: 'branding',
let: { 'assets': '$assets' },
pipeline: [
{
$match: {
$expr: {
$and: [
{ $in: [ '$_id', '$$assets' ] },
{ $ne: [ '$uid', user.uid ] }
]
}
}
}
],
as: 'assets'
}
},
{ $unwind: '$assets' },
{
$replaceRoot: {
newRoot: {
$mergeObjects: ['$assets', { acl: '$acl' }, { teamID: '$teamID' }, { owner: '$owner' }]
}
}
},
{
$project: {
assets: 1,
acl: {
$cond: {
if: { $eq: [ user._id, '$owner' ] },
then: 'owner',
else: '$acl'
}
},
_id: 1,
uid: 1,
title: 1,
teamID: 1,
logos: 1,
fonts: 1
}
}
]);
return [...personalAssets, ...sharedAssets];
Отдельные активы:
const personalAsset = await AssetsModel.findOne({ _id, uid: user.uid });
if (brand) asset.acl = 'owner';
const sharedAsset = await MemberModel.aggregate([
{
$match: {
$and: [
{ teamID: mongoose.Types.ObjectId(teamID) },
{
$or: [
{ uid: user.uid },
{ owner: user._id }
]
}
]
}
},
{ $project: { _id: 0, acl: 1, teamID: 1, owner: 1 } },
{
$lookup: {
from: 'team',
pipeline: [
{
$match: {
$expr: {
$and: [
{ $eq: [ '$_id', mongoose.Types.ObjectId(teamID) ] },
{ $in: [ mongoose.Types.ObjectId(_id), '$assets' ] }
]
}
}
},
{ $project: { _id: 0, assets: 1, teamID: teamID } }
],
as: 'team'
}
},
{ $replaceRoot: { newRoot: { $mergeObjects: [ { $arrayElemAt: [ '$team', 0 ] }, '$$ROOT' ] } } },
{ $match: { 'assets.0': { $exists: true } } },
{
$lookup: {
from: 'branding',
pipeline: [
{
$match: {
$expr: {
$eq: [ '$_id', mongoose.Types.ObjectId(_id) ]
}
}
}
],
as: 'assets'
}
},
{ $unwind: '$assets' },
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
'$assets',
{
acl: {
$cond: {
if: { $eq: [ user._id, '$owner' ] },
then: 'owner',
else: '$acl'
}
}
},
{ teamID: '$teamID' }
]
}
}
}
]);
brand = sharedAsset[0];
То же самое касается папок и проектов. Основная проблема здесь заключается в получении списка активов и получении членского ACL (уровня управления доступом). Это проще сделать для отдельных ресурсов (папок и проектов).
Мой главный вопрос заключается в том, как структурировать мою схему и запрашивать их более масштабируемым способом.
Она еще не опубликована, поэтому у меня есть возможность переписать схему для всего вышеперечисленного, кроме схемы пользователя. Как вы предлагаете денормализовать схему, а затем запрашивать/агрегировать в соответствии с тем, что я объяснил выше?
Не могли бы вы поделиться, какая у вас реальная проблема. Какой-то конкретный запрос/агрегация, которого вы не смогли достичь? Если это производительность, что является узким местом? и Т. Д.
@WanBachtiar Я только что отредактировал свой вопрос и добавил запрос, который использую для схемы активов. У меня все запросы работают, меня больше всего беспокоит его масштабируемость в будущем. Например, в ближайшие месяцы будет добавлена совместная работа в режиме реального времени, и я хочу, чтобы мои данные были структурированы таким образом, чтобы их было легче расширять.
Хотя агрегации должны хорошо работать с индексированными ключами в $match, вы также можете рассмотреть возможность параллельного запуска нескольких запросов на чтение и объединения их в своем приложении. (Если у вас есть отношения на основе ссылок в корневой модели). Время поиска Mongo для индексированных ключей очень быстрое. Nodejs делает асинхронное параллельное выполнение второй натурой для вас (он был разработан для такого типа вещей). Время отклика не будет таким медленным, как вы могли бы ожидать по привычке. Примечание: ваша схема может быть упрощена для более подходящего моделирования NoSQL.
@Shasak Я уже использую параллельные асинхронные запросы, которые отлично работают, когда я извлекаю отдельные документы. Сложная часть — выборка массивов, а затем сопоставление списка контроля доступа с результатами актива или члена. Агрегированные запросы выполняют свою работу. Я просто надеюсь, что они тоже масштабируемы. Для вашего примечания, как вы предлагаете денормализировать его, чтобы упростить моделирование и сделать его более подходящим для NoSQL?





Я использовал $lookup для объединения нескольких коллекций. Если вы выбираете базу данных nosql, такую как mongo, ваша текущая схема imo не самая лучшая. Во-первых, немного сложно выразить свои запросы с помощью агрегата. Во-вторых, его не так просто поддерживать по мере роста ваших требований. Я бы посоветовал перейти на rdbms, прежде чем он станет слишком пустым или денормализует вашу текущую схему. Монго никогда не предназначался для того, чего вы пытаетесь достичь.