[跟进这个问题和评论:Should entity have methods and if so how to prevent them from being called outside aggregate]
正如标题所说:我不清楚实体作为一个孩子的实际/精确目的是什么?
根据我在很多地方读到的内容,这些是实体的属性,它是聚合的子代:
在我看来,这转化为几个问题:
那么,为什么我们只有一个实体而不是Value Objects呢?只有值对象,聚合上的所有方法以及公开值对象(我们已经复制实体信息)的接缝要方便得多。
PS。我想关注聚合上的子实体,而不是实体的集合。
[更新以回应Constantin Galbenu的回答和评论]
那么,实际上,你会有这样的事情吗?
public class Aggregate {
...
private _someNestedEntity;
public SomeNestedEntityImmutableState EntityState {
get {
return this._someNestedEntity.getState();
}
}
public ChangeSomethingOnNestedEntity(params) {
this._someNestedEntity.someCommandMethod(params);
}
}
- 它具有聚合的本地身份
从逻辑上讲,可能,但具体地用持久性手段实现这一点,我们所拥有的往往是不必要的复杂。
- 我们需要一个只读副本Value-Object来公开来自实体的信息(例如,至少为了能够读取它以便保存到db的存储库)
不一定,您可以拥有只读实体。
another question已经解决了问题的存储库部分。读取不是问题,并且有多种技术可以防止来自外部世界的写入访问,但仍允许持久层直接或间接地填充实体。
那么,为什么我们只有一个实体而不是Value Objects呢?
你可能会有点仓促地把问题放在同一个篮子里,这个问题确实略有不同
仅仅因为值对象最好是不可变的并且不强制执行聚合级别不变量(尽管它们确实强制执行它们自己的数据完整性)并不意味着实体不能对某些相同特征进行微调组合。
你正在考虑数据。不要那么做。 :)实体和值对象不是数据。它们是您可以用来为问题域建模的对象。实体和价值对象只是对问题进行建模时自然产生的事物的分类。
实体应该是私有的以进行聚合
是。此外,对象中的所有状态都应该是私有的,并且不能从外部访问。
我们需要一个只读副本Value-Object来公开来自实体的信息(例如,至少为了能够读取它以便保存到db的存储库)
不。我们不会公开已有的信息。如果信息已经可用,那意味着某人已经对此负责。所以联系那个对象为你做事,你不需要数据!这基本上是Law of Demeter告诉我们的。
经常实施的“存储库”确实需要访问数据,你是对的。他们是一个糟糕的模式。它们通常与ORM结合使用,在这种情况下更糟糕,因为您失去了对数据的所有控制权。
我们在实体上的方法在Aggregate上重复(反之亦然,我们必须在Aggregate上处理实体的方法在实体上重复)
诀窍是,你不必。你创建的每个对象(类)都是有原因的。如前所述,要创建额外的抽象,请为域的一部分建模。如果你这样做,那么存在于更高抽象层次上的“聚合”对象永远不会想要提供与下面的对象相同的方法。这意味着没有任何抽象。
这种用例仅在创建面向数据的对象时出现,这些对象除了保存数据之外几乎没有其他用处。显然,如果你无法获取数据,你会想知道如何对这些做任何事情。但是,这是一个很好的指标,表明您的设计尚未完成。
- 实体应该是私有的以进行聚合
是。我不认为这是一个问题。继续阅读以了解原因。
- 我们需要一个只读副本Value-Object来公开来自实体的信息(例如,至少为了能够读取它以便保存到db的存储库)
否。使聚合返回需要持久化的数据和/或需要在聚合的每个方法的事件中引发的数据。
原始的例子。真实世界需要更精细的响应,也许performMove
函数需要使用game.performMove
的输出来构建persistence
和eventPublisher的propper结构:
public void performMove(String gameId, String playerId, Move move) {
Game game = this.gameRepository.load(gameId); //Game is the AR
List<event> events = game.performMove(playerId, move); //Do something
persistence.apply(events) //events contains ID's of entities so the persistence is able to apply the event and save changes usign the ID's and changed data wich comes in the event too.
this.eventPublisher.publish(events); //notify that something happens to the rest of the system
}
对内部实体做同样的事情。让实体重新调整已更改的数据,因为其方法调用(包括其ID)会在AR中捕获此数据并为持久性和eventPublisher构建propper输出。这样,您甚至不需要将具有实体ID的公共只读属性暴露给AR,而AR也不需要将其内部数据暴露给应用程序服务。这是摆脱Getter / Setters包对象的方法。
- 我们在实体上的方法在Aggregate上重复(反之亦然,我们必须在Aggregate上处理实体的方法在实体上重复)
有时,业务规则(检查和应用)仅属于一个实体及其内部状态,AR仅作为网关。没关系,但如果你发现这个模式太多,那么它就是错误AR设计的标志。也许内部实体应该是AR而不是内部实体,也许你需要将AR分成几个AR(其中一个是旧的实体),等等......不要害怕只有一个类只有一个或两种方法。
响应dee zg评论:
persistence.apply(事件)究竟做了什么?它只保存整个聚合或实体吗?
都不是。聚合和实体是域概念,而不是持久性概念;你可以拥有文档存储,列存储,关系等,不需要匹配1到1你的域概念。你没有从persitence中读取聚合和实体;使用从持久性中获取的数据在内存中构建聚合和实体。聚合本身不需要持久化,这只是一个可能的实现细节。请记住,聚合只是一个组织业务规则的构造,它不是一个表示状态的表示。
您的事件具有上下文(用户意图)和已更改的数据(以及在持久性中识别事物所需的ID),因此在知道的持久层中编写apply
函数是非常容易的,即在案例中使用sql指令关系数据库,执行什么以应用事件并保留更改。
您能否提供示例何时及为何更好(或甚至不可避免?)使用子实体而不是其Id作为值对象引用的单独AR?
为什么要设计和建模具有状态和行为的类?
抽象,封装,重用等基本的SOLID设计。如果实体具有确保操作的域规则和不变量所需的一切,则该实体是该操作的AR。如果您需要实体无法完成的额外域规则检查(即实体没有足够的内部状态来完成检查,或者自然不适合实体和代表什么)那么您必须重新设计;有时可能会对进行额外域规则检查的聚合进行建模,并将其他域规则检查委托给内部实体,有时可能会更改实体以包含新内容。它太依赖于域上下文,所以我不能说有一个固定的重新设计策略。
请记住,您不会在代码中为聚合和实体建模。您只对具有行为的类建模,以检查域规则以及执行该更改所需的检查和响应所需的状态。 Theese类可以作为不同操作的聚合或实体。 Theese术语仅用于帮助沟通和理解类在每个操作上下文中的作用。当然,您可能处于操作不适合实体的情况,您可以使用V.O模拟聚合。持久性ID,它是可以的(遗憾的是,在DDD中,在不知道域上下文的情况下,几乎所有内容都是正常的)。
你想从一个比我更好解释事情的人那里获得更多的启示吗? (不是母语为英语的人是这些复杂问题的障碍)看看这里:
https://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-1 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-2 http://blog.sapiensworks.com/post/2016/07/14/DDD-Aggregate-Decoded-3
您在CQRS体系结构中不存在这些问题,其中Write模型(Aggregate)与Read模型不同。在扁平体系结构中,Aggregate必须公开读/查询方法,否则它将毫无意义。
- 实体应该是私有的以进行聚合
是的,通过这种方式,您清楚地表达了它们不适合外部使用的事实。
- 我们需要一个只读副本Value-Object来公开来自实体的信息(例如,至少为了能够读取它以便保存到db的存储库)
存储库是一种特殊情况,不应该以与应用程序/演示文稿代码相同的方式查看。它们可以是同一个包/模块的一部分,换句话说,它们应该能够访问嵌套的实体。
可以将实体视为/实现为具有不可变ID的对象和表示其状态的Value对象,类似于此(在伪代码中):
class SomeNestedEntity
{
private readonly ID;
private SomeNestedEntityImmutableState state;
public getState(){ return state; }
public someCommandMethod(){ state = state.mutateSomehow(); }
}
所以你看?您可以安全地返回嵌套实体的state
,因为它是不可变的。 Law of Demeter会有一些问题,但这是你必须作出的决定;如果你通过返回状态来打破它,你可以使代码第一次编写更简单,但耦合会增加。
- 我们在实体上的方法在Aggregate上重复(反之亦然,我们必须在Aggregate上处理实体的方法在实体上重复)
是的,这可以保护Aggregate的封装,也允许Aggregate保护它的不变量。
我不会写得太多。只是一个例子。一辆汽车和一个装备。汽车是聚合根。装备是儿童实体