问题描述
在DDD中设计聚合时,经验法则是什么?
What would be a rule of thumb when designing an aggregate in DDD?
根据Martin Fowler的说法,聚合是可以被视为域对象的域对象的集群。单个单元。聚合将具有其组成对象之一作为聚合根。
According to Martin Fowler, aggregate is a cluster of domain objects that can be treated as a single unit. An aggregate will have one of its component objects be the aggregate root.
在设计了大约20个DDD项目之后,我仍然对
After designing aproximatelly 20 DDD projects I am still confused about the rule of thumb when choosing domain objects that would create an aggregate.
Martin Fowler使用订单和订单项类比,但我认为这不是一个好例子,因为order +线条项目实际上是紧密边界的对象。在该示例中没有太多思考。
Martin Fowler uses order and line-items analogy and I don't think it is a good example, because order+line-items are really tightly bounded objects. Not much to think about in that example.
让我们尝试一下汽车类比,其中CarContent是汽车经销商域的子域。
Lets try with car analogy where CarContent is a subdomain of a car dealer domain.
CarContent将至少包含一个或多个聚合。
CarContent would consist of at least one or more aggregate/s.
例如,我们有这个AggregateRoot(我会尽可能简化)
For example we have this AggregateRoot (i am keeping it as simple as possible)
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;}
}
也可以是这样(示例B)
Alternative could be this (example 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;}
}
无需设备即可创建汽车,但无法激活/无需设备即可发布(即可以在两个不同的交易中填充)
Car can be created without equipment but it cannot be activated/published without the equipment (ie. this can be populated over two different transactions)
可以在示例A中通过CarStructureAggregate引用设备,或者在示例B中使用CarEquipmentAggregate引用设备。
Equipment can be referenced trough CarStructureAggregate in example A or using CarEquipmentAggregate in example B.
EquipmentType可以是枚举,也可以是具有更多类,属性的复杂类。
EquipmentType could be an enum, or could be a complex class with many more classes, properties.
什么是规则在示例A和B之间进行选择时的经验?
现在,想象一下汽车可能有更多信息,例如
What is a rule of thumb when choosing between examples A and B?Now imagine that car could have more information such as
- 照片
- 描述
- 也许有关引擎的更多数据
而CarStructureAggregate可能是一个非常大的类
and CarStructureAggregate could be an extremely large class
那么是什么使我们将集合划分为新的集合?尺寸?事务的原子性(尽管这不会成为问题,因为通常同一子域的聚合通常位于同一服务器上)
So what is it that makes us split Aggregate into new Aggregates? Size? Atomicity of a transaction (although that would not be an issue since usually aggregates of a same sub domain are usually located on the same server)
推荐答案
要注意不要太过坚强的面向对象思想。蓝皮书和马丁·福勒(Martin Fowler)的职位有些陈旧,它提供的视野太狭窄。
Be careful about having too strong OO mindset. The blue book and Martin Fowler post are a little bit old and the vision it provides is too narrow.
聚合不一定是一类。它不需要持久化。这些是实施细节。甚至有时总的来说,这并不意味着改变,而只是意味着可以执行此操作。
An aggregate does not need to be a class. It does not need to be persisted. Theese are implementation details. Even, sometimes, the aggregate do things that does not implies a change, just implies a "OK this action may be done".
iTollu帖子为您提供了一个良好的开端:重要的是交易边界。聚合的工作仅仅是一项。在大多数情况下(请记住并非总是如此),请确保操作中的不变式和域规则会更改必须保留的数据。事务边界意味着一旦汇总表明某事可能且已经完成;世界上没有任何东西应该与之矛盾,因为如果发生矛盾,那么您的集合体设计不当,与集合体矛盾的规则应该成为集合体的一部分。
iTollu post give you a good start: What matters is transactional boundary. The job of an aggregate is just one. Ensure invariants and domain rules in an action that, in most of the cases (remember that not always), change data that must be persisted. The transactional boundary means that once the aggregate says that something may, and has, be done; nothing in the world should contradict it because, if contradiction occurs, your aggregate is badly designed and the rule that contradict the aggregate should be part of aggregate.
因此,为了设计聚合,我通常会非常简单并不断发展。考虑一个静态函数,该函数可以获取检查操作的域规则并返回域事件表示已完成操作所需的所有VO,实体和命令数据(除实体的唯一ID外,几乎都是DTO)。事件的数据必须包含系统必要的所有数据,以保留更改(如果需要),并在事件到达其他聚合(在相同或不同的有界上下文中)时采取行动。
So, to design aggregates, I usualy start very simple and keep evolving. Think in a static function that recives all the VO's, entities and command data (almost DTO all of them except the unique ID of the entities) needed to check domain rules for the action and returns a domain event saying that something has be done. The data of the event must contain all data that your system needs to persist the changes, if needed, and to act in consequence when the event reach to other aggregates (in the same or different bounded context).
现在开始重构和面向对象设计。 Supress原始痴迷反模式。添加约束以避免实体和VO的错误状态。用来检查或计算与实体相关的东西的那段代码更好地进入了实体。 。将需要几乎相同的VO和实体的静态函数一起检查域规则,以创建一个类作为聚合根。使用存储库以始终有效的状态创建聚合。还有很长的时间等等。只是良好的OOP设计,没有DTO,告诉,不要问前提,责任分离等等。
Now start to refactoring and OO designing. Supress primitive obsession antipattern. Add constraints to avoid incorrect states of entities and VO's. That piece of code to check or calculate someting related to a entity better goes into the entity. Put your events in a diet. Put static functions that need almost the same VO's and entities to check domain rules together creating a class as aggregate root. Use repositories to create the aggregates in an always valid state. And a long etc. You know; just good OOP design, going towards no DTO's, "tell, don't ask" premise, responsibility segregation and so on.
完成所有工作后,您会发现您的聚合,VO和实体是根据域(与受限制的上下文相关)和技术角度完美设计的。
When you finish all that work you will find your aggregates, VO's and entities perfectly designed from a domain (bounded context related) and technical view.
这篇关于如何在DDD中正确定义聚合?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!