Architecture 如何正确处理事件源中的聚合关系?

Architecture 如何正确处理事件源中的聚合关系?,architecture,domain-driven-design,rdbms,cqrs,event-sourcing,Architecture,Domain Driven Design,Rdbms,Cqrs,Event Sourcing,当拥有某种程度上“复杂”的领域模型时,不可避免地会有相关的实体(这就是聚合根的意义)。但我应该如何从事件中重建关系?在序列化事件中按其他聚合id搜索显然不是一个选项 我有一个使用这种DB结构的想法(例如)。它只使用id和外键聚合表,以简化从不同实体检索所有必要事件的过程。这不是违反了ES原则吗 DDD说,“富域模型”由处理业务逻辑的实体组成,我们在对域对象建模时使用它们 是的,当您使用事件源时仍然如此 所以我可以想象,即order.addItem(product)方法,其中OrderItem创

当拥有某种程度上“复杂”的领域模型时,不可避免地会有相关的实体(这就是聚合根的意义)。但我应该如何从事件中重建关系?在序列化事件中按其他聚合id搜索显然不是一个选项

我有一个使用这种DB结构的想法(例如)。它只使用id和外键聚合表,以简化从不同实体检索所有必要事件的过程。这不是违反了ES原则吗

DDD说,“富域模型”由处理业务逻辑的实体组成,我们在对域对象建模时使用它们

是的,当您使用事件源时仍然如此

所以我可以想象,即order.addItem(product)方法,其中OrderItem创建并建立与order和product的关系

啊哈,不,两种方法都不行。如果订单和产品是不同的聚合,则您不会使用
Order.addItem(Product)
拼写。订单和产品不对产品的状态承担责任。或者,换句话说,我们从不在彼此内部筑巢

根据DDD的规则,通常的拼写是
order.addItem(product.id)
。请注意这一重要区别:更改订单无法更改产品的任何细节

这是问题的一部分:域中的每一个状态都有一个负责保持其一致性的机构

注意:
order.addItem(product)
的拼写对于一个模型是有意义的,其中product是一个实体,它不是聚合根,而是从属于order(更准确地说,每个产品只与一个订单关联)


但若项目和订单有多对一关系,那个么它不应该包含orderId吗?(不是包含ItemId列表的订单)。但这意味着它应该是Item.AddToOrder(order.Id),这没有多大意义

简单的回答是,根据您决定如何对数据建模,以及哪些项负责维护聚合的完整性,这些方法会有不同的拼写

向后工作——聚合的部分动机(而不仅仅是在整个模型周围有一个大的一致性边界)是并发修改模型不同部分的想法。OrderItem是否是Order的单独聚合将部分取决于两个不同的OrderItem可以同时修改的重要性

对于像在线购物车这样的情况,这可能不是非常重要

对于多个参与方试图同时修改相同顺序的设置,如

OrderItem.create(order.id, product.id)
这不是不合理的


而且,聚合根包含其他聚合,在这种情况下OrderItem是聚合的,而不是聚合根或值对象

聚合根对一个聚合负责。该聚合(基本上只是“状态”)在概念上可以是从属于根的多个实体的当前状态,每个实体管理整体的特定部分

如果我是对的,聚合根包含控制内部聚合的业务逻辑,所以我们仍然需要构建包含其他实体的聚合根

“内部聚合”毫无意义——聚合不会嵌套。实体嵌套,最外层的实体扮演聚合根的角色

那么,我们如何构建嵌套实体呢

让我们退一步,看看我们通常如何创建单个实体。我们运行一些查询(比如getById)来获取之前保存的状态

Factory {
    Entity fromState(currentState) {
        return new (entity);
    }

    State fromEntity(theEntity) {
        return theEntity.getState();
    }
}
嵌套实体的工作方式相同,从属实体接管部分工作。对于订单,它可能看起来像

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
    }
} 
工厂{
Order fromState(当前状态){
列表项=。。。
对于(State.Item itemState:currentState.items()){
OrderItem OrderItem=OrderItem.from(itemState)
items.add(orderItem)
}
退回新订单(项目);
}
}
命令{
State getState(){
State currentState=State.EMPTY;
for(OrderItem OrderItem:this.items){
currentState=currentState.addItemState(orderItem.getState())
返回电流状态
}
} 
当我们使用事件源时,改变的一点是我们使用事件集合而不是状态

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
    }
}
工厂{
事件顺序(历史){
//一个棘手的问题——我们将关注的历史
//at是所有实体的历史记录的混合体
//协调对聚合的更改,因此我们可能需要
//解开这个问题。
Map=itemHistories
对于(事件e:历史)
items.put(e.orderItemId,e)
列表项=。。。
对于(事件:itemHistories.values){
OrderItem OrderItem=OrderItem.from(事件)
items.add(orderItem)
}
退回新订单(项目);
}
}
命令{
列出getEvents(){
列表事件=新列表();
for(OrderItem OrderItem:this.items){
events.addAll(orderItem.getEvents())
}
返回事件
}
}

您会处理与密钥的任何关系,但它们不会像数据库中的外键约束那样强制执行。事实上,您的商店事件中用户的密钥可能是人类可读的ID。不需要通过GUID链接


从一个到另一个的映射是在投影中完成的,投影可以帮助您在命令中显示正确的信息和正确的字段。

什么是“ES原则”?仅在写入模型中存储事件?我认为您将它们都混淆了。在ES中,所有事件都存储在
事件存储
中。然后
聚合
a.k.a.
写入模型
被重新识别