一切似乎都是聚合根

时间:2014-11-03 20:22:37

标签: domain-driven-design

我想在餐厅模拟菜单组成。我围绕3个概念创建了一个非常小的有界上下文:MenuCategoryProduct

菜单由不同的产品组成,每个菜单中的每个产品都放在某个类别下(例如,类别为& #34;参赛者","第一道菜","第二道菜","甜点" ...)。

问题在于,对我来说,一切似乎都是实体

例如,删除菜单时,不会删除任何产品或类别。当其他3个概念出现时也是如此。

关于UI,菜单将像层次结构一样被使用:

Menu1
    Category1
        Product1
        Product2
    Category2
        Product3
        Product4

我想知道如何对此进行建模。我应该制作3个聚合体吗?然后,如何构建菜单以便像上面的层次结构一样被消费?

感谢。

2 个答案:

答案 0 :(得分:4)

如果你有一个菜单作为当前安排的聚合根,那么如果你想在不同的菜单上重复使用相同的产品会怎样?从聚合根目录之外,您将无法持有对它的引用。

相反,我认为有菜单和产品(问厨师他会称之为食物或菜单项目!)作为聚合根源是好的,因为它们都存在并且有自己的身份(随着时间的推移,你可能会有不同的菜单,菜单可能包含已在其他菜单上使用过的食品。

将类别作为价值对象可能更合适(同样,也许厨师会称之为课程?) - 你有多少类别(我相信这些课程可能是固定的)?一个类别是否有自己的生命周期和身份?每种产品的可枚举都能很好地满足这一要求。

您还提到了删除......这听起来像是技术基础设施术语。这有两个问题:

  1. 如果您执行了上述操作并拆分为两个聚合根(菜单和产品),当菜单引用时,您将如何处理删除产品?这成为跨两个聚合根的事务,难以实现(DDD的主要思想之一是定义事务边界和通过聚合强制执行不变量 - 来自外部的聚合根上的任何外部操作都应使其保持一致状态)。

  2. 想想厨师或餐厅经理会做什么 - 他们会删除一个菜单,还是会将其存档或让它不再可供选择?

  3. 有助于巩固上述内容的一项资源是Vaughn Vernon的有效聚合设计 - http://dddcommunity.org/library/vernon_2011/

    就代码而言,C#中的伪造模型将在下面:

    public Object HandleGetMenuCommand(
        string menuName,
        IMenuRepository menuRepository,
        ProductRepository productRepostiory)
    {
        Menu menu = menuRepository.Get(new MenuId(menuName));
        List<ProductIds> productsInMenu = menu.Products;
        List<Products> products = productRepostiory.GetMany(productsInMenu);
        List<string> categories = ... // get a list of unique categories from products
    
        // now assemble products by category...
        foreach(var category in categories)
        {
                var productsInCategory in products.Find(x => x.Category == category);
                foreach(var product in productsInCategory)
                {
                    // add this to a list...
                }
        }
    
        var clientData = new {
            name = menu.Name,
            // add in the products that were assembled by category above
        }
    
        return clientData;
    }
    

答案 1 :(得分:3)

我认为你正在努力将所有东西都放入DDD,而不是仅通过调查“现实世界”来关注这个领域的简单性。请让我先用简单的OO方法然后让我们谈谈如何用DDD看到它。 所以你想拥有菜单,所以餐厅的顾客可以看看他们并选择他们想吃的东西。我们假设目前午餐,晚餐,每个工作日只有一个菜单。 餐厅经理应该能够创建此菜单并随时更改它。 这是我们的第一个用例

menu = new Menu("monday lunch", starters, mainEntrees, desserts, drinks);

其中每个参数都是值对象列表(乍一看)。 只要看看现实世界,菜单就是你可以在餐馆得到的食物的描述,这些描述是不可变的对象(价值对象)所以到目前为止我们不需要任何其他东西。 此时我们只有一个聚合根是Menu。它具有全局标识并对一组对象进行分组并具有不变性。我会用什么课来模拟主菜,甜点? String ...到目前为止String的一个实例已经足够了。 菜单怎么样?我们可以有很多菜单,一个用于星期一,一个用于星期二,一个用于星期五晚上,等等,每个都有自己的全局标识,所以我们肯定有MenuRepository。 如果餐厅经理想要添加新的甜点怎么样?

menu = menuRepository.get(menuId);
menu.add(aDessert); 

就够了。 到目前为止,我们有一个Menu聚合根及其存储库。 我想这就是你的要求。

但是,我认为你错过了价格。菜单上的每一行都有价格,如果午餐或晚餐,不同菜单上的两个相同的甜点可能会有不同的价格,所以更实际的方法似乎是:

menu = new Menu("monday lunch", starters, mainEntrees, desserts, drinks);

其中starters,mainEntrees,甜点,饮品是MenuLine类型的集合。 如果客户告诉服务员给他带些东西,会发生什么?需要创建新订单以保持客户要求的进度,以便稍后创建发票。 这个订单需要知道客户要求的食物,我的意思是客户订购的菜单项。到目前为止,我们正在使用价值对象来模拟甜点,饮料等。现在我们应该为客户可以选择的那些选项提供一些形式或标识符,以便我们在订单等订单上下文中使用一些唯一的标识符。这是我们确定需要另一个聚合根(您称之为Product)的地方。我宁愿称它为食物或类似的东西。 因此,当餐厅经理决定让餐厅提供新食物时,系统就会

food = new Food("sushi");
foodRepository.add(food);

然后我们经理想在周一晚上的菜单中添加寿司

food = foodRepository.getBy(foodId);
menu = menuRepository.findByCriteria(mondayNight)
menu.addAt(food, tenDollars);

以及Menu做的是

addAt(food, tenDollars) {
mainEntrees.add(new MenuItem(food.id, food.description, tenDollars)
}

我们不希望菜单保存对Food聚合根的引用,因为vaughn vernon在其书中所说的内容。

希望它有所帮助。您根本不需要类别的概念:

Menu {
List starters;
List entrees;
List desserts;
List drinks;
}

希望它有所帮助。

干杯, 塞巴斯蒂安