【问题标题】:Does DDD violate some OOP principles?DDD 是否违反了某些 OOP 原则?
【发布时间】:2021-06-30 15:42:58
【问题描述】:

也许我可能对一些基本原则感到困惑或误解。在基于 Axon 的事件溯源项目中应用 DDD 时,您定义了多个聚合,每个聚合具有多个命令处理程序方法来验证聚合的状态更改是否有效,以及应用每个请求的状态更改的相应事件处理程序方法集合。

编辑开始: 所以聚合可能看起来像

@Aggregate
public class Customer {

    @AggregateIdentifier
    private CustomerId customerId;
    
    private Name name;
    

    @CommandHandler
    private Customer(CreateCustomerCommand command) {
        AggregateLifecycle.apply(new CustomerCreatedEvent(
                command.getCustomerId(), command.getName());
    }
    
    @EventSourcingHandler
    private void handleEvent(CustomerCreatedEvent event) {
        this.customerId = event.getCustomerId();
        this.name = event.getName();
    }
    
}

编辑结束

所以我的第一个问题是:我是否正确得出这样的结论:聚合没有直接实现任何状态更改方法(典型的公共方法会更改聚合实例的属性),但是所有状态更改方法都定义在与交互的单独域服务中Axon 的命令网关?

编辑开始: 换句话说,聚合是否应该定义负责向框架发送命令的 setter,这将导致通过将以下代码添加到聚合来调用相应的命令处理程序和事件处理程序?

public void setName(String name){
    commandGateway.send(new UpdateNameCommand(this.customerId, name));
}

@CommandHandler
private void handleCommand(UpdateNameCommand command) {
    AggregateLifecycle.apply(new NameUpdatedEvent(command.getCustomerId(), command.getName());
}
        
@EventSourcingHandler
private void handleEvent(NameUpdatedEvent event) {
   this.name = event.getName();
}

这似乎违反了一些建议,因为需要从聚合中引用网关。

或者您通常会在单独的服务类中定义此类方法,然后将命令发送到框架。

@Service
public class CustomerService {
    
    @Autowired
    private CommandGateway gateway;
    
    public void createCustomer(String name) {
        CreateCustomerCommand command = new CreateCustomerCommand(name);
        gateway.send(command);      
    }
    
    public void changeName(CustomerId customerId, String name) {
        UpdateNameCommand command = new UpdateNameCommand (customerId, name);
        gateway.send(command);
    }
    
}

这对我来说似乎是正确的方法。这似乎使得(至少在我看来)外部世界无法直接访问聚合的行为(所有命令和事件处理程序方法都可以设为私有),就像一个更“传统”的对象,它们是请求状态更改的入口点...

编辑结束

其次:这是否与定义其(公共)接口方法以与之交互的每个类的 OOP 原则相矛盾?换句话说,这种方法是不是让一个对象或多或少成为一个无法直接交互的转储对象?

感谢您的反馈, 库尔特

【问题讨论】:

    标签: oop domain-driven-design event-sourcing axon


    【解决方案1】:

    所以我的第一个问题是:我是否正确得出这样的结论:聚合没有直接实现任何状态更改方法(典型的公共方法会更改聚合实例的属性),但是所有状态更改方法都定义在与Axon 的命令网关?

    绝对不是!聚合本身负责任何状态更改。

    在使用事件溯源时,误解可能是围绕聚合中的命令处理程序(方法)。在这种情况下,命令处理程序(方法)不应直接应用更改。相反,它应该应用一个事件,然后在同一个聚合实例中调用一个事件源处理程序(方法)来应用状态更改。

    无论是否使用事件溯源,聚合都应该只公开动作(例如命令处理程序),并根据这些动作做出决定。最好,(命令处理程序的)聚合不会暴露聚合边界之外的任何状态。

    其次:这是否与定义其(公共)接口方法以与之交互的每个类的 OOP 原则相矛盾?换句话说,这种方法是不是让一个对象或多或少成为一个无法直接交互的转储对象?

    如果聚合依赖外部组件来管理其状态,情况就会如此。但事实并非如此。

    问题编辑后的其他反应

    所以我的第一个问题是:我是否正确得出这样的结论:聚合没有直接实现任何状态更改方法(典型的公共方法会更改聚合实例的属性),但所有状态更改方法都在单独的域服务交互中定义用 Axon 的命令网关?

    我认为恰恰相反。问题中的第一个聚合是一个聚合的示例,该聚合在其内部具有所有状态更改操作。将 setter 暴露给“改变状态”是一个非常糟糕的想法。它强制逻辑决定何时在聚合之外更改此值。 在我看来是对 OOP 的“违反”。

    不应指示在 DDD 和/或 CQRS 上下文中的聚合更改状态。相反,他们应该收到实际的业务意图来做出反应。这就是命令应该反映的内容:业务意图。因此,聚合可能会更改某些属性,但只是为了确保之后发生的任何命令的行为方式都反映了之前发生的事情。使用事件源聚合,有一个额外的中间步骤:应用事件。需要这样做以确保从过去的决策中获取聚合产生完全相同的状态。另请注意,这些事件不是“状态更改决策”,而是“业务决策”。状态变化就是这样的结果。

    决赛

    最后显示的服务类将是典型的交互。发送命令的组件,不直接与 Aggregate 实例交互。但是,将“UpdateNameCommand”与前面示例中的“setName”进行比较让我感到厌烦,因为命令不应该是“C(R)UD”操作,而是实际的业务操作。在可能的情况下,UpdateName 就是这样一个业务操作。

    【讨论】:

    • 我理解你的意思。似乎我的第一个问题表述得不够清楚,无法描述我的困惑。我用其他信息/代码示例更新了我的原始帖子,以更好地描述我的误解。
    • 说实话,编辑后我变得更加困惑。我添加到我的回复中。我希望这能澄清我的观点。
    • "不应指示聚合在 DDD 和/或 CQRS 上下文中更改状态。相反,它们应接收实际的业务意图以做出反应……因此,聚合可能会更改一些属性,但只是为了确保之后发生的任何命令的行为方式都反映了之前发生的事情......”是我一直在寻找的答案:-)
    猜你喜欢
    • 2020-07-11
    • 2014-02-15
    • 2010-11-29
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    相关资源
    最近更新 更多