如何正确处理事件采购中的聚合关系?

时间:2017-06-08 14:29:30

标签: architecture domain-driven-design rdbms cqrs event-sourcing

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

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

enter image description here

2 个答案:

答案 0 :(得分:6)

  

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)

不合理。

  

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

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

  

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

"内部聚合"没有意义 - 聚合体不会嵌套。 实体嵌套,最外面的实体扮演聚合根的角色。

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

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

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
    }
} 

当我们使用事件来源时,改变的是我们使用事件集合而不是状态。

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
    }
}

答案 1 :(得分:0)

您处理与键的任何关系,但它们不会像DB中的外键约束那样强制执行。事实上,用户在您的商店活动中的关键可能是人类可读的ID。没有要求通过Guids链接。

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

相关问题