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

时间:2019-02-06 12:53:45

标签: node.js database mongodb mongoose graphql

试图克服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 });

这些可以在关系数据库中轻松实现,但是由于我的应用已经公开使用,拥有超过4万名用户,因此迁移到另一个数据库并不是一个容易的选择。

这些可以在MongoDB中实现吗?或者我应该停止尝试并移至另一个DB?

我已经在使用猫鼬了,但是我也愿意使用本地的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(访问控制级别)时。对于单个资产(文件夹和项目)更容易做到这一点。

我的主要问题是如何以更可扩展的方式构造模式并查询它们。

0 个答案:

没有答案