如何在DDD CQRS 事件采购方法中处理业务规则



我正在尝试弄清楚如何使用CQRS/ES方法处理复杂的域模型。让我们想象我们有例如订单域实体,该实体处理秩序的状态和行为。它具有带有过渡规则的状态属性,用于在状态之间切换(实现状态模式或任何其他类型的状态机器(。根据DDD原则,该逻辑应以类别(代表顺序模型(本身的方式实现,具有approve()cancel()ship()等方法。

查看这种类型体系结构的不同公共示例,事实证明,域实体和总体根源是相同的,并且可以处理状态和行为,甚至可以从事件中进行投射。这不是违反SRP吗?

但是我的问题更具体:如果我想处理新命令(并应用新事件(,是否应该从事件流重新构建实体(即从写入模型和写入db(并调用其行为方法(将事件应用于状态(处理业务规则?或者只是本身处理命令和事件,而没有任何写作模型实体?

伪码说明:

class ApproveOrderHandler
{
    private EventStore eventStore
    // ...
    public void handle(ApproveOrder event)
    {
        Order order = this.eventStore.findById(event.getOrderId()); // getting order projection from event store
        order.approve(); // handling business logic
        this.eventStore.save(order.releaseEvents()); // save new events (OrderApproved)
    }
}
class Order extends AbstractAggregate
{
    private Uuid id;
    private DateTime takenAt;
    private OrderStatus status;
    // ...
    public void approve()
    {
        this.status.approve(); // business rules blah blah
        this.Apply(new OrderApproved(this.id)); // applying event
    }
    // ...
}

不是过度的吗?

我应该如何处理事件源中实体之间的关系?如果它们仅存在于"读取模型"中,则域实体类中没有意义。

编辑:或者也许我应该将状态快照存储在"读取数据库"中并从中恢复实体?但这打破了"阅读不同模型"的想法...

edit2:固定的读/写模型错误

tl; dr

但是我的问题更具体:如果我想处理新命令(并应用新事件(,是否应该从事件流重新构建实体(即从写入模型和写入db(并调用其行为方法(将事件应用于状态(处理业务规则?

是。

或只是处理命令和事件本身,而没有任何写模式实体?

no。

再次有感觉

命令处理程序生活在应用程序组件中;业务模型生活在域组件中。

保持这些组件分开的动机:使模型更换成本有效。域专家关心的是企业获得其 win 的是域模型。我们不会期望撰写一次业务模型并在所有时间内纠正它 - 我们更有可能了解有关该模型如何工作的更多信息,因此定期对模型进行改进。因此,重要的是,没有很多阻力将模型的一个版本替换为另一个版本 - 我们希望替换很容易。我们希望将更改反映在我们获得的业务价值中所需的工作量。

所以我们希望好东西与"管道"分开。

将所有业务逻辑保留在域组件中,可为您带来两个简单的胜利;首先,您永远不必猜测业务逻辑的生活在哪里 - 用例的细节很容易或硬,业务逻辑将按顺序而不是其他任何地方。其次,由于业务逻辑不在命令处理程序中,因此您不必担心创建一堆测试双打以满足这些依赖性要求 - 您可以直接针对域模型进行测试。

因此,我们使用处理程序来重建实体并调用其业务逻辑方法,而不是处理业务逻辑本身?

几乎 - 我们使用存储库来重建实体并汇总以处理业务逻辑。命令处理程序的角色是编排;这是 data 模型与 domain 模型之间的胶水。

查看这种类型体系结构的不同公共示例,事实证明,域实体和总体根源是相同的,并且可以处理状态和行为,甚至可以从事件中进行投射。这不是违反SRP吗?

不,没有。"责任"是一个模糊的术语,但在这种情况下,意味着"改变的理由",而总体根源只有一种(一种(改变的理由:业务需求改变。不影响聚集根的更改理由的一个例子是基础架构的更改,即您更改为事件存储从MySql更改为CC_4。

但是我的问题更具体:如果我想处理新命令(并应用新事件(,是否应该从事件流重新构建实体(即从写入模型和写入db(并调用其行为方法(将事件应用于状态(处理业务规则?

每次命令都达到一个汇总,该汇总实例是从其事件流重构(从Event store加载的 - 写入侧持续性(,通过在命令它们是生成的;可以进行优化作为快照,但应避免使用,直到有必要证明为止。

或只是处理命令和事件本身,而没有任何写模式实体?

您需要拥有一个写模型实体,又称汇总;该模型通过拒绝与先前生成的事件的不兼容的命令来执行业务规则。

您的伪代码应该看起来像这样:

class ApproveOrderHandler
{
    private EventStore eventStore
    // ...
    public void handle(ApproveOrder event)
    {
        Order order = this.eventStore.findById(event.getOrderId()); // getting order projection from event store
        order.approve(); // handling business logic
        this.eventStore.save(order.releaseEvents()); // save new events (OrderApproved)
    }
}
class Order extends AbstractAggregate
{
    private Uuid id;
    private DateTime takenAt;
    private OrderStatus status;
    // ...
    public void approve()
    {
        if(!this.canBeApproved){ //here is a business rule enforced!
            throw new Exception('Order cannot be approved');
        }
        if(this.status.isAlreadyApproved()){
             return; //idempotent operation
        }
        // this line of code was moved to its own Apply method
        this.generateAndApplyEvent(new OrderApproved(this.id)); // applying event
    }
    //this method is called in two situations: when the aggregate is reconstructed from the eventstream and when the event is raised for the first time
    public void Apply(OrderApproved event)
    {
        this.status.approve(); // transition change
    }
    // ...
}

不是过度的吗?

不,不是。请注意,我移动了正在更改订单状态的代码行

我应该如何处理事件源中实体之间的关系?如果它们仅存在于"读取模型"中,则域实体类中没有意义。

在写入模型中也存在实体之间(聚集根之间(之间的关系,但引用仅由ID

编辑:或者也许我应该将状态快照存储在"读取数据库"中并从中恢复实体?但这打破了"阅读不同模型"的想法...

聚合快照(当激活/使用时(通常沿事件流中存储在事件提交中(事件提交由单个命令执行生成的所有事件组成(。从我在制作中看到的内容来看,快照每n-th提交都存储(例如每5个提交(。因此它们存储在写作方面。这是因为快照仅在特定的汇总版本的上下文中具有含义。

将您的业务逻辑放在实体或价值上。在那里为域服务而努力。

最新更新