猫鼬方案设计多对多

时间:2019-01-18 14:34:55

标签: mongodb mongoose database-design

我正在尝试为任务管理应用程序设计一个猫鼬方案架构/设计。

所需的功能和模型:

型号:

  • 用户-有木板
  • 委员会-有列表
  • 列表-有卡片

功能:

  • 用户可以创建一个木板
  • 用户可以在面板中创建列表
  • 用户可以在列表中创建卡
  • 用户可以将成员添加到董事会中
  • 用户可以从董事会成员中将成员添加到卡中
  • 用户可以删除董事会,删除所有列表和卡片以及删除成员
  • 用户可以删除列表,删除列表中的所有卡
  • 用户可以删除卡,从卡中删除所有成员
  • 用户可以更新面板中的列表位置(排序)
  • 用户可以更新列表中的卡片位置(排序)

  • 其他功能(可选)

    • 用户可以在卡片中添加评论
    • 卡片活动记录(移动,编辑,评论)

我知道有很多问题要问,但是如何设计这种功能的方案呢?

将父母作为参考是最好的解决方案吗?

例如。

用户:

{
 name: 'Bob',
 _id: '2626'
}

委员会:

{
 name: 'Board name',
 _id: '123456',
 members: [
  { type: ObjectID, ref: 'user' } // '2626'
 ]
}

列表:

{
 name: 'List name',
 _id: '2525',
 boardId: { type: ObjectID, ref: 'board' } // '123456'
}

卡:

{
 name: 'Card name',
 boardId: { type: ObjectID, ref: 'board' } // '123456',
 listId: { type: ObjectID, ref: 'list' } // '2525'
}

如何查询这种类型的结构?

  • 1)按用户ID获取板卡列表
  • 2)通过董事会ID获取列表列表
  • 3)按板卡ID和列表ID获取卡列表

因此,对于董事会而言,我会去抓取所有列表,但随后我必须去获得每个列表的所有卡片,这似乎并不是很有效。

也许进入棋盘视图时,我应该仅按棋盘ID查询卡,但是如何获取列表,并将每张卡放入自己的列表中?

如何处理删除卡或将卡从一个列表移动到另一个列表?

请不要对我施加压力,我对mongodb的世界真的很陌生,但是我确实在尽力而为。

1 个答案:

答案 0 :(得分:1)

您定义的架构非常好,这就是流程的样子。

最初,当用户登录时,您需要向他们显示面板列表。这应该很容易,因为您只需使用board集合中的user_id进行查找查询即可。

Board.find({members: user_id}) // where user_id is the ID of the user

现在,当用户单击特定的面板时,您可以使用board_id来获得列表,类似于上面的查询。

List.find({boardId: board_id}) // where board_id is the ID of the board

同样,您可以在list_id和board_id的帮助下获得卡片。 Card.find({boardId:board_id,listId:list_id})//其中board_id是木板的ID,listId是列表的ID

现在,让我们看一下您可能需要同时来自2个或更多集合的数据的情况。 例如,当用户单击板上时,您不仅需要板上的列表,还需要板上的卡片。 在这种情况下,您需要这样编写汇总,

            Board.aggregate([
            // get boards which match a particular user_id
            {
                $match: {members: user_id}
            },
            // get lists that match the board_id
            {
                $lookup:
                {
                    from: 'list',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'lists'
                }
            }
        ])

这将返回板,并且在每个板中,将有与该板相关的一系列列表。如果某个特定的板没有列表,那么它将有一个空数组。

类似地,如果您想将卡片添加到列表和板上,则聚合查询将是一个更复杂的漫游器,

            Board.aggregate([
            // get boards which match a particular user_id
            {
                $match: {members: user_id}
            },
            // get lists that match the board_id
            {
                $lookup:
                {
                    from: 'list',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'lists'
                }
            },
            // get cards that match the board_id
            {
                $lookup:
                {
                    from: 'card',
                    localField: '_id',
                    foreignField: 'boardId',
                    as: 'cards'
                }
            }
        ])

这还将在混合中添加一系列纸牌。同样,您也可以获取列表中的卡片。

现在,让我们考虑一下这是否是最佳方案。我个人认为您建议的模式非常好,因为另一种解决方法是将ID存储在父集合中,这将使您可以使用填充来获取数据,而不是查找查询。

例如,将清单ID存储在板集合中。不利的一面是,每当添加新列表时,您都需要在列表集合中添加该列表,并更新该列表所连接的电路板(添加列表ID),我认为这太麻烦了。

最后,关于您给出的架构的一些建议, 我认为您应该在每个集合中添加user_id(创建者的ID),因为在许多情况下,您需要显示创建该特定面板或列表或其他内容的用户的名称,并且还具有将用户添加到其中的功能。一张特定的卡片,等等,我认为您应该有两个字段,一个是creator_id,另一个应该是related_users,这将是一个数组(显然,您可以选择更好的名称)。

您应该在卡片和其他要按位置排序的集合中添加位置字段。该字段应该是数字。

到现在,删除卡或将其从一个列表移动到另一个列表应该非常容易且不言自明。

编辑1:基于评论
您无需在聚合之后“将卡片分配到列表”,就可以在聚合本身中完成此操作,因此就像这样,

Board.aggregate([
    // get boards which match a particular user_id
    {
        $match: { members: user_id }
    },
    // get lists that match the board_id
    {
        $lookup:
        {
            from: 'list',
            localField: '_id',
            foreignField: 'boardId',
            as: 'lists'
        }
    },
    // unwind lists (because it's an array at the moment)
    {
        $unwind: '$lists'
    },
    // Now you have object named lists in every board object
    // get cards that match the list_id (note that the cards contain list_id)
    {
        $lookup:
        {
            from: 'card',
            localField: '_id',
            foreignField: 'listId',
            as: 'cards'
        }
    },
    // now merge back the objects and get a simple object for each boardID
    {
        $group: {
            _id: "$_id",
            members: { $addToSet: "$members" },
            lists: { $addToSet: "$lists" }
        }
    }
])

这会给你这样的东西,

data = {
    '_id': '123456',
    'members': [
        {
            name: 'Bob',
            _id: '2626'
        },
        {
            name: 'Matthew',
            _id: '2627'
        }
    ],
    'lists': [
        {
            name: 'List 1',
            _id: '2525',
            boardId: '123456',
            cards: [
                {
                    name: 'Card 1',
                    boardId: '123456',
                    listId: '2525'
                },
                {
                    name: 'Card 2',
                    boardId: '123456',
                    listId: '2525'
                }
            ]
        },
        {
            name: 'List 2',
            _id: '2526',
            boardId: '123456',
            cards: [
                {
                    name: 'Card 3',
                    boardId: '123456',
                    listId: '2525'
                },
                {
                    name: 'Card 4',
                    boardId: '123456',
                    listId: '2525'
                }
            ]
        }
    ]
}

因此,基本上,您可以在单个查询中获取列表以及这些列表的卡片,这非常有效。

现在进入您要求的两个查询,

  1. 卡从一个列表移动到另一个列表,只需将卡文档中的listId字段编辑为新的listID(实际上很简单)。

  2. 卡在列表中上移了一个位置

正如我说的,如果您想要位置,则需要在文档中添加一个称为位置的字段,然后每当移动卡时,都需要更改这些卡的“位置”值。 在聚合中,您只需要添加另一个名为“ $ sort”的阶段并根据位置值对其进行排序。 这将有点乏味,因为每当您向上移动卡时,也需要更新卡的上方位置。