DDD 是否违反了某些 OOP 原则?

Does DDD violate some OOP principles?

也许我对一些基本原则感到困惑或误解。在基于 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();
    }
    
}

编辑结束

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

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

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();
}

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

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

@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);
    }
    
}

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

编辑结束

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

感谢您的反馈, 库尔特

So my first question is: Am I correct to conclude the aggregate does not implement any state changing methods directly (typical public methods altering the properties of the aggregate instance) but all state changing methods are defined in a separate domain service interacting with Axon's command gateway?

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

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

无论是否使用事件溯源,聚合都应仅公开操作(例如命令处理程序),并根据这些操作做出决策。优选地,(命令处理程序的)聚合不会在聚合边界之外公开 any 状态。

And secondly: Isn't this in contradiction with OOP principles of each class defining its (public) interface methods to interact with? In other words, doesn't this approach make an object a more or less dump object in which direct interaction is impossible?

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

问题编辑后的其他反应

So my first question is: Am I correct to conclude the aggregate does not implement any state-changing methods directly (typical public methods altering the properties of the aggregate instance) but all state changing methods are defined in a separate domain service interacting with Axon's command gateway?

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

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

最终评论

最后显示的服务class就是典型的交互。发送命令的组件,不直接与聚合实例交互。但是,将“UpdateNameCommand”与前面示例中的“setName”进行比较让我很反感,因为命令不应该是“C(R)UD”操作,而是实际的业务操作。 UpdateName 可能就是这样的业务操作。