我想在餐厅模拟菜单组成。我围绕3个概念创建了一个非常小的有界上下文:Menu
,Category
,Product
。
菜单由不同的产品组成,每个菜单中的每个产品都放在某个类别下(例如,类别为& #34;参赛者","第一道菜","第二道菜","甜点" ...)。
问题在于,对我来说,一切似乎都是实体。
例如,删除菜单时,不会删除任何产品或类别。当其他3个概念出现时也是如此。
关于UI,菜单将像层次结构一样被使用:
Menu1
Category1
Product1
Product2
Category2
Product3
Product4
我想知道如何对此进行建模。我应该制作3个聚合体吗?然后,如何构建菜单以便像上面的层次结构一样被消费?
感谢。
答案 0 :(得分:4)
如果你有一个菜单作为当前安排的聚合根,那么如果你想在不同的菜单上重复使用相同的产品会怎样?从聚合根目录之外,您将无法持有对它的引用。
相反,我认为有菜单和产品(问厨师他会称之为食物或菜单项目!)作为聚合根源是好的,因为它们都存在并且有自己的身份(随着时间的推移,你可能会有不同的菜单,菜单可能包含已在其他菜单上使用过的食品。
将类别作为价值对象可能更合适(同样,也许厨师会称之为课程?) - 你有多少类别(我相信这些课程可能是固定的)?一个类别是否有自己的生命周期和身份?每种产品的可枚举都能很好地满足这一要求。
您还提到了删除......这听起来像是技术基础设施术语。这有两个问题:
如果您执行了上述操作并拆分为两个聚合根(菜单和产品),当菜单引用时,您将如何处理删除产品?这成为跨两个聚合根的事务,难以实现(DDD的主要思想之一是定义事务边界和通过聚合强制执行不变量 - 来自外部的聚合根上的任何外部操作都应使其保持一致状态)。
想想厨师或餐厅经理会做什么 - 他们会删除一个菜单,还是会将其存档或让它不再可供选择?
有助于巩固上述内容的一项资源是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;
}
希望它有所帮助。
干杯, 塞巴斯蒂安