MongoDB - структурирование и запрос моделей и схем для множества отношений - PullRequest
0 голосов
/ 06 февраля 2019

Пытаясь побороть недостатки отношений в MongoDB, вот что я пытаюсь иметь в своей базе данных:

Модели:

  1. Пользователи:(в базе данных уже есть много пользователей), в основном запрашивается через uid, но также доступен _id.
  2. Команды: содержит 1 владельца, нескольких участников, проекты и активы.
  3. Участники: могут принадлежать нескольким командам, каждый участник может иметь разные роли.Участник может принадлежать нескольким командам с разными ролями.
  4. Роли: Принадлежит членам.Участник может иметь различную роль в каждой команде, к которой он принадлежит.['view', 'edit', 'admin']. Роли также могут быть встроены в модель Members для использования гибридного метода.
  5. Проекты: проекты могут быть независимыми или входить в состав команды, доступ к проектам, принадлежащим командам,определяется ролью участника.
  6. Активы: активы могут быть независимыми или входить в состав команды, доступ к активам, принадлежащим командам, определяется ролью участника.Актив также может содержать несколько проектов.

Технические характеристики:

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

Член команды может иметь доступ к активам и проектам, к которым владелец команды решил, что он может иметь доступ.

Участник может создаватьих собственные личные активы и проекты.

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

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

Участник может просматривать список ресурсов и проектов, которыми он поделился в команде.

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

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

Текущая схема:

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 участника (уровень контроля доступа).Это проще сделать для отдельных ресурсов (папок и проектов).

Мой главный вопрос - как структурировать мою схему и запросить ее более масштабируемым образом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...