Пытаясь побороть недостатки отношений в MongoDB, вот что я пытаюсь иметь в своей базе данных:
Модели:
- Пользователи:(в базе данных уже есть много пользователей), в основном запрашивается через
uid
, но также доступен _id
. - Команды: содержит 1 владельца, нескольких участников, проекты и активы.
- Участники: могут принадлежать нескольким командам, каждый участник может иметь разные роли.Участник может принадлежать нескольким командам с разными ролями.
- Роли: Принадлежит членам.Участник может иметь различную роль в каждой команде, к которой он принадлежит.['view', 'edit', 'admin']. Роли также могут быть встроены в модель Members для использования гибридного метода.
- Проекты: проекты могут быть независимыми или входить в состав команды, доступ к проектам, принадлежащим командам,определяется ролью участника.
- Активы: активы могут быть независимыми или входить в состав команды, доступ к активам, принадлежащим командам, определяется ролью участника.Актив также может содержать несколько проектов.
Технические характеристики:
Каждый пользователь может быть членом команды или независимым пользователем приложения.Обычные пользователи могут иметь свои собственные частные проекты и активы.
Член команды может иметь доступ к активам и проектам, к которым владелец команды решил, что он может иметь доступ.
Участник может создаватьих собственные личные активы и проекты.
В зависимости от роли участника, участник также может делиться активами с членами своей команды.
Участник, в зависимости от своих ролей, может добавлять, удалять, редактировать активыкоманды, удаление или переименование команды, добавление или удаление участников из команды.
Участник может просматривать список ресурсов и проектов, которыми он поделился в команде.
Участникможет просматривать одну страницу ресурсов, где они могут добавлять элементы удаления в актив в зависимости от своих ролей.
Участник может просматривать, редактировать, добавлять, удалять отдельные проекты в зависимости от своих ролей.
Текущая схема:
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 });
Это может быть легко достигнуто в реляционных базах данных, но так как мое приложение уже открыто с более чем 40Kпользователи, переход на другую БД - непростая задача.
Можно ли добиться этого в 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 участника (уровень контроля доступа).Это проще сделать для отдельных ресурсов (папок и проектов).
Мой главный вопрос - как структурировать мою схему и запросить ее более масштабируемым образом.