领域建模中容易出错的部分


领域建模是考虑实体和它们之间的关系?啊,不太好。 虽然这通常是典型的建议的例子,它实际上是落后的。 不要构建数据库模式/结构。

如何对复杂的领域进行建模?在领域驱动设计中,您在领域建模时可能会考虑实体、值对象和聚合。但你如何定义这些呢?我将回顾一下什么是常见的建模方法,但它是落后的。

现在让我解释一下原因。

DDD领域建模在,通常建议是先弄清楚你的聚合是什么。您定义实体和值对象,然后将它们组合在一起以定义聚合。

聚合是形成一致性边界的相关对象(实体和值对象)的集群。聚合已经受到了太多的关注。它们是一个很棒的模式,但不幸的是,它经常被误解,并且经常在不应该使用的情况下使用,从而导致复杂性。

然而,如前所述,这通常是人们在对域进行建模时开始采用的第一条路线,即开始弄清楚实体和值对象的聚合是什么。这是完全错误的起点。

案例:一个晚餐托管应用程序

  • 最初定义的是实体,并且是名词驱动的。
  • 然后获取每个实体并确定它是否是聚合根。

聚合根是一个实体,是通往聚合的网关。与消费者/调用者的所有交互都是通过根实体完成的。

以晚餐实体为例

  • 如果晚餐实体是聚合根,则:晚餐将包含一系列参与者、一系列晚餐以及可能的晚餐评论集合。
  • 如果菜单实体是聚合根,则。。。
  • 如果客人实体是聚合根,则。。。

总之,穷尽可能性:确定什么是真正的根,什么是真正仅供参考的。

但是,菜单聚合中的晚餐实际上不可能存在于其中。因此,我们将其转换为值对象,并且实际上只是将其引用到晚餐Dinner聚合。

{
    "id": 123,
   
"name": "My Menu",
   
"description": "My Test Menu",
   
"averageRating": 4.5,
   
"items": [
        {
           
"name": "Burger",
           
"price": 5.99
        }
    ],
   
"hostID": 456,
   
"dinnerIDs": [
        789,
        456
    ],
   
"reviews": [
        ...
    ]
}

这是整个过程,通常作为在系统中构建聚合的方法的建议。

猜猜你刚刚建造了什么?数据表结构!但是它不是一个聚合。

后向方法
我说过我认为上述过程是一种落后的做法。
为什么?
这个晚宴主持系统是做什么的?当然,您可以猜测这是一种举办晚宴、邀请客人参与晚宴并对晚宴进行评分的方式。但真正构成这个系统的实际功能是什么?你不知道,因为这些都没有被实际定义。

当您需要一致性边界时,聚合非常有用。这意味着您需要一致性,因为您需要执行某种类型的状态更改,并且聚合作为一个整体需要一致地持久化。你为什么要改变状态?如果您可以或不能执行状态更改,哪些不变量会驱动?

要回答这个问题,您需要知道哪些行为正在驱动状态变化,哪些行为正在驱动不变量。
在不了解行为是什么的情况下用实体和值对象定义聚合是落后的。所有你拥有的数据桶(对象)。最终,您的行为将驱动您的实体和值对象中拥有哪些数据,这将驱动您定义聚合的方式。

行为决定了您要封装的数据。

如果您是由实体驱动的,那么您的“行为”最终将聚合于 CRUD。

class Menu
{
  Menu Create();
  void AddDinner();
  void RemoveDinner(Dinner dinner);
  void UpdateItem(Item item);
}

当您拥有规范化的数据结构时,您通常需要将所有数据组合(连接)在一起,以便在应用程序中发挥作用。

行为应该指导您的设计
我们关注的是行为和业务规则,而不是数据。

例如晚餐中你应该关注:

  • 你是否需要有一位主人并让客人进行预订,客人可能会取消预订。
  • 座位数量可能有限。如果您没有填满所有座位会怎样?

晚餐会有些复杂,因为你需要有一位主人并让客人进行预订。他们可能会取消预订。座位数量可能有限。如果您没有填满所有座位会怎样?

如果没有行为,您只是定义数据(基础)结构/模式和关系。不要假装它是一个集合;当你不需要的时候就不要使用它。你只是增加了无用的复杂性。