Architecture 如何在DDD和x2B中处理业务规则;CQRS+;事件来源方法?

Architecture 如何在DDD和x2B中处理业务规则;CQRS+;事件来源方法?,architecture,domain-driven-design,cqrs,event-sourcing,business-rules,Architecture,Domain Driven Design,Cqrs,Event Sourcing,Business Rules,我试图弄清楚如何使用CQRS/ES方法处理复杂的领域模型。让我们想象一下,我们有一个Order域实体,它处理订单的状态和行为。它有一个Status属性,带有用于在状态(实现或任何其他类型的状态机)之间切换的转换规则。根据DDD原则,此逻辑应该在订单类(表示订单模型)本身中实现,具有类似于approve()、cancel()、ship()等方法 在这种体系结构中,域实体和聚合根是相同的,它处理状态和行为,甚至处理自己从事件中的投影。这不是违反了SRP吗 但我的问题更为具体:如果我想处理新命令(并应

我试图弄清楚如何使用CQRS/ES方法处理复杂的领域模型。让我们想象一下,我们有一个Order域实体,它处理订单的状态和行为。它有一个Status属性,带有用于在状态(实现或任何其他类型的状态机)之间切换的转换规则。根据DDD原则,此逻辑应该在订单类(表示订单模型)本身中实现,具有类似于
approve()
cancel()
ship()
等方法

在这种体系结构中,域实体和聚合根是相同的,它处理状态和行为,甚至处理自己从事件中的投影。这不是违反了SRP吗


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

用于说明以下内容的伪代码:

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;博士
但我的问题更为具体:如果我想处理新命令(并应用新事件),我是否应该从事件流(即从写模型和写数据库)中重建实体,并调用其行为方法(将事件应用到状态)来处理业务规则

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

没有

再一次,带着感觉 命令处理程序位于应用程序组件中;业务模型位于域组件中

保持这些组件分离的动机是:使模型替换具有成本效益。领域专家关心的是,业务从何处获得成功,是领域模型。我们不希望只编写一次业务模型,就可以一直正确地使用它——更可能的是,我们将了解更多关于我们希望模型如何工作的信息,并因此定期对模型进行改进。因此,重要的是,用另一个版本的模型替换一个版本时不要有太多的阻力——我们希望替换变得容易;我们希望进行更改所需的工作量反映在我们获得的业务价值中

因此,我们希望好的东西从“管道”中分离出来

将所有业务逻辑保留在域组件中可以让您轻松获得两个胜利;首先,您不必猜测业务逻辑的位置——不管用例的细节是简单的还是困难的,业务逻辑都是按顺序进行的,而不是其他任何地方。第二,因为业务逻辑不在命令处理程序中,所以您不必担心创建一堆测试双工来满足这些依赖性需求——您可以直接针对域模型进行测试

所以,我们使用处理程序来重建实体并调用它们的业务逻辑方法,而不是处理业务逻辑本身

几乎——我们使用存储库来重组实体和聚合以处理业务逻辑。命令处理程序的角色是编排;它是数据模型和域模型之间的粘合剂

查看这种体系结构的不同公共示例,发现域实体和聚合根是相同的,它处理状态和行为,甚至处理自己从事件中的投影。这不是违反了SRP吗

不,没有。“责任”是一个模糊的术语,但在本例中是指“变更的原因”,聚合根只有一个(某种)变更原因:业务需求变更。一个不影响聚合根的更改原因是基础结构更改,即您将事件存储实现从
MySql
更改为
MongoDB


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

每次命令到达聚合时,该聚合实例都会根据其事件流(从
事件存储库
-写端持久性加载)进行重构,方法是按生成顺序逐个应用;可以像快照一样进行优化,但在证明必要之前应该避免

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

您需要有一个写模型实体,也称为聚合;该模型通过拒绝与先前生成的事件不兼容的命令来强制执行业务规则

您的伪代码应该如下所示:

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
进行

编辑:或者我应该将状态快照存储在“读取数据库”中,并从中恢复操作的实体?但它打破了“不同的读写模式”的观念