mko*_*mko 6 domain-driven-design ddd-repositories
在 DDD 中设计聚合时的经验法则是什么?
根据 Martin Fowler 的说法,聚合是一组可以被视为单个单元的域对象。聚合将其组件对象之一作为聚合根。
https://martinfowler.com/bliki/DDD_Aggregate.html
在设计了大约 20 个 DDD 项目后,在选择将创建聚合的域对象时,我仍然对经验法则感到困惑。
Martin Fowler 使用订单和订单项进行类比,我认为这不是一个很好的例子,因为订单+订单项确实是紧密绑定的对象。在那个例子中不需要考虑太多。
让我们尝试用汽车类比,其中 CarContent 是汽车经销商域的子域。
CarContent 将由至少一个或多个聚合组成。
例如,我们有这个 AggregateRoot(我尽可能保持简单)
class CarStructureAggregate
{
public int Id {get; private set;}
public ModelType ModelType {get; private set;}
public int Year {get; private set;}
public List<EquipmentType> {get; private set;}
}
Run Code Online (Sandbox Code Playgroud)
替代方案可能是这个(示例B)
class CarStructureAggregate
{
public int Id {get; private set;}
public ModelType ModelType {get; private set;}
public int Year {get; private set;}
}
class CarEquipmentAggregate
{
public int Id {get; private set;}
public List<EquipmentType> {get; private set;}
}
Run Code Online (Sandbox Code Playgroud)
可以在没有设备的情况下创建汽车,但没有设备就无法激活/发布(即,这可以通过两个不同的事务填充)
可以通过示例 A 中的 CarStructureAggregate 或使用示例 B 中的 CarEquipmentAggregate 引用设备。
EquipmentType 可以是枚举,也可以是具有更多类、属性的复杂类。
在示例 A 和 B 之间进行选择时的经验法则是什么?现在想象一下汽车可以有更多的信息,比如
和 CarStructureAggregate 可能是一个非常大的类
那么是什么让我们将 Aggregate 拆分为新的 Aggregate 呢?尺寸?事务的原子性(尽管这不是问题,因为通常同一子域的聚合通常位于同一服务器上)
小心不要有过于强烈的 OO 心态。蓝皮书和马丁福勒的帖子有点旧,它提供的视野太窄了。
聚合不需要是一个类。它不需要持久化。这些是实现细节。甚至,有时,聚合做的事情并不意味着改变,只是意味着“可以完成这个动作”。
iTollu 帖子为您提供了一个良好的开端:重要的是交易边界。聚合的工作只是其中之一。确保操作中的不变量和域规则在大多数情况下(请记住并非总是如此)更改必须持久化的数据。事务边界意味着一旦聚合表明某事可能并且已经完成;世界上没有任何东西应该与它相矛盾,因为如果发生矛盾,则您的聚合设计得很糟糕,与该聚合相矛盾的规则应该是聚合的一部分。
所以,为了设计聚合,我通常从非常简单的开始并不断发展。考虑一个静态函数,它接收检查操作的域规则所需的所有 VO、实体和命令数据(除了实体的唯一 ID 之外,几乎所有这些都是 DTO),并返回一个域事件,表明某些事情已经完成。事件的数据必须包含您的系统需要的所有数据,如果需要,系统需要保留更改,并在事件到达其他聚合(在相同或不同的有界上下文中)时采取相应的行动。
现在开始重构和面向对象设计。抑制原始的痴迷反模式。添加约束以避免实体和 VO 的错误状态。用于检查或计算与实体相关的某些内容的那段代码更好地进入实体。把你的活动放在节食中。将需要几乎相同的 VO 和实体来检查域规则的静态函数放在一起,创建一个类作为聚合根。使用存储库以始终有效的状态创建聚合。还有很长的等等。你知道;只是好的 OOP 设计,没有 DTO,“告诉,不要问”的前提,责任分离等等。
当您完成所有这些工作时,您会发现您的聚合、VO 和实体是从域(有界上下文相关)和技术视图完美设计的。
我相信,这里重要的是事务边界。
一方面,您不能将其设置得比足以保持聚合的一致性更窄。
另一方面,您也不希望将其设置得太大而导致用户无法进行并发修改。
在您的示例中,如果用户应该能够同时修改 CarStructure 和 CarEquipment - 那么我会坚持使用实现 B。如果不能 - 使用 A 会更简单。
| 归档时间: |
|
| 查看次数: |
4285 次 |
| 最近记录: |