zug*_*ugo 4 architecture rdbms domain-driven-design cqrs event-sourcing
当具有某种"复杂"的域模型时,拥有相关实体是不可避免的(这是聚合根的意义).但是我应该如何从事件中重建关系呢?在序列化事件中搜索其他聚合ID显然不是一种选择.
我有一个想法,使用这样的数据库结构(例如).它只聚合具有id和外键的表,以简化从不同实体检索所有必要事件的过程.这不违反ES原则吗?
DDD说"富域模型"由处理业务逻辑的实体组成,我们在为域对象建模时使用它们
是的,当您使用事件采购时,这仍然是正确的.
所以我可以设想ie order.addItem(product)方法,其中OrderItem创建并与Order和Product建立关系.
啊哈,不 - 不是两种方法.如果订单和产品是不同的聚合,则order.addItem(product)不是您要使用的拼写.订单和产品不对产品状态负责.或者,换句话说,我们永远不会将聚合根嵌套在彼此之内.
根据DDD的规则,通常的拼写是order.addItem(product.id).请注意这一重要区别:更改订单不能更改产品的任何细节.
这是重点之一:域中的每个状态都有一个负责维护其一致性的权限.
注意:拼写order.addItem(product)对于一个模型是有意义的,其中Product是一个不是聚合根的实体,而是从属于Order(更确切地说,每个Product只与一个订单相关联).
但是如果Item与Order有多对一的关系,那么它不应该包含orderId吗?(不包含ItemId列表的订单).但这意味着它应该是Item.AddToOrder(order.Id),这没有多大意义.
简短的回答是,根据您决定对数据建模的方式以及哪些项目负责维护聚合的完整性,您可以获得不同的方法拼写.
向后工作 - 聚合的动机的一部分(而不是仅仅围绕整个模型具有一个大的一致性边界)是同时修改模型的不同部分的想法.OrderItem是否是来自Order的单独聚合将部分取决于可以同时修改两个不同OrderItem的重要性.
对于像在线购物车这样的情况,这可能不是非常重要.
对于许多方试图同时修改相同订单的设置,例如
OrderItem.create(order.id, product.id)
Run Code Online (Sandbox Code Playgroud)
这不是不合理的.
而且,聚合根包含其他聚合,在这种情况下,OrderItem是聚合的,而不是聚合根或值对象.
聚合根负责一个聚合.这个聚合(从根本上说只是"状态")在概念上可以是从属于根的多个实体的当前状态,每个实体管理整个特定部分.
如果我是对的,聚合根包含控制内部聚合的业务逻辑,因此我们仍然需要构建包含其他实体的聚合根.
"内部聚合"没有意义 - 聚合体不会嵌套. 实体嵌套,最外层实体扮演聚合根的角色.
那么,我们如何构建嵌套实体?
让我们退后一步,看看我们通常如何创建一个单一的实体.我们运行一些查询(如getById)获得国家,我们以前保存的.
Factory {
Entity fromState(currentState) {
return new (entity);
}
State fromEntity(theEntity) {
return theEntity.getState();
}
}
Run Code Online (Sandbox Code Playgroud)
嵌套实体以相同的方式工作,下级实体接管部分工作.对于订单,它可能看起来像......
Factory {
Order fromState(currentState) {
List<OrderItem> items = ...
for (State.Item itemState : currentState.items()) {
OrderItem orderItem = OrderItem.from(itemState)
items.add(orderItem)
}
return new Order(items);
}
}
Order {
State getState() {
State currentState = State.EMPTY;
for(OrderItem orderItem : this.items) {
currentState = currentState.addItemState(orderItem.getState())
return currentState
}
}
Run Code Online (Sandbox Code Playgroud)
当我们使用事件源时,改变的是我们使用事件集合而不是状态.
Factory {
Order fromEvents(history) {
// The one tricky bit -- the history we will be looking
// at is a mix of histories from all of the entities that
// coordinate the changes to the aggregate, so we may need
// to untangle that.
Map<OrderItemId, Events> = itemHistories
for (Event e : history )
items.put(e.orderItemId, e)
List<OrderItem> items = ...
for (Events events: itemHistories.values) {
OrderItem orderItem = OrderItem.from(events)
items.add(orderItem)
}
return new Order(items);
}
}
Order {
List<Event> getEvents () {
List<Event> events = new List();
for(OrderItem orderItem : this.items) {
events.addAll(orderItem.getEvents())
}
return events
}
}
Run Code Online (Sandbox Code Playgroud)