MongoDB - 为多对多关系构建和查询模型和模式

Hoo*_*ari 6 database mongoose mongodb node.js graphql

试图解决MongoDB中关系的缺点,这是我在我的数据库中尝试的:

楷模:

  1. 用户:(已经有很多用户在数据库中)大多通过他们查询,uid_id也可用.
  2. 团队:包含1个所有者,多个成员,项目和资产.
  3. 成员:可以属于多个团队,每个成员可以拥有不同的角色.成员可以属于具有不同角色的多个团队.
  4. 角色:属于成员.成员可以在他们所属的每个团队中拥有不同的角色.['view','edit','admin'].角色也可以嵌入到成员模型中以使用混合方法.
  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 });
Run Code Online (Sandbox Code Playgroud)

这些可以在关系数据库中轻松实现,但由于我的应用程序已经超过4万用户,因此转移到另一个数据库并不是一个简单的选择.

这些可以在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];
Run Code Online (Sandbox Code Playgroud)

单一资产:

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];
Run Code Online (Sandbox Code Playgroud)

文件夹和项目也是如此.这里的主要挑战是获取资产列表并获取成员ACL(访问控制级别).对单个资产(文件夹和项目)执行此操作更容易.

我的主要问题是如何构建我的架构并以更可扩展的方式查询它们.