聚合根工厂方法 return 可以是命令而不是发布事件吗?

Can an Aggregate Root factory method return a command instead of publishing an event?

在 Vaughn Vernon 的 Implementing Domain-Driven Design 书中,他描述了在聚合根中使用工厂方法。一个例子是 Forum 聚合根具有 startDiscussion 工厂方法,该方法 return 编辑了 Discussion 聚合根。

public class Forum extends Entity  {

    ...

    public Discussion startDiscussion(
      DiscussionId aDiscussionId, Author anAuthor, String aSubject) {

        if (this.isClosed()) {
            throw new IllegalStateException("Forum is closed.");
        }

        Discussion discussion = new Discussion(
          this.tenant(), this.forumId(), aDiscussionId, anAuthor, aSubject);

        DomainEventPublisher.instance().publish(new DiscussionStarted(...));

        return discussion;    
    }

如何在事件溯源系统中实现这种工厂模式,特别是在 Axon 中?

我相信按照惯例,可以这样实现:

StartDiscussionCommand -> DiscussionStartedEvent -> CreateDiscussionCommand -> DiscussionCreatedEvent

我们触发 StartDiscussionCommandForum 处理,Forum 然后发布 DiscussionStartedEvent。外部事件处理程序将捕获 DiscussionStartedEvent、转换它并触发 CreateDiscussionCommand。另一个处理程序将使用 CreateDiscussionCommand 实例化 Discussion 并且 Discussion 将触发 DiscussionCreatedEvent.

或者,我们能否改为: StartDiscussionCommand -> CreateDiscussionCommand -> DiscussionCreatedEvent

我们触发 StartDiscussionCommand,这将触发命令处理程序并调用 Forum 的 startDiscussion() 方法,该方法将 return CreateDiscussionCommand。处理程序然后将调度此 CreateDiscussionCommand。另一个处理程序接收命令并使用它来实例化 DiscussionDiscussion 然后会触发 DiscussionCreatedEvent.

第一种做法涉及4个DTO,而第二种做法只涉及3个DTO。

有没有关于应该首选哪种做法的想法?或者还有其他方法吗?

Any thoughts on which practice should be preferred?

命令的动机是指示应用程序更新记录簿。您不希望产生事件的命令非常奇怪。

也就是说,如果你的流量是

Forum.startDiscussion -> []
Discussion.create -> [ DiscussionCreated ]

有人肯定会问为什么要参与论坛?

if (this.isClosed()) {
    throw new IllegalStateException("Forum is closed.");
}

这是一种错觉——我们正在查看论坛在过去任意时间点的状态以处理讨论命令。换句话说,在这个检查之后,论坛的状态可能会改变,而我们在讨论中的处理就不知道了。因此,在验证命令时或通过在讨论中检查读取模型时进行此检查同样正确。

(我们从记录簿中得到的一切都是过去的代表;它必须是,以便已经在记录簿中供我们阅读。我们在当下行动的唯一时刻是我们更新记录簿的那一点。更准确地说,是在写入的那一刻,我们发现我们对过去所做的假设是否仍然成立。当我们将更改写入讨论时,我们正在证明讨论自从我们读取数据以来没有发生变化;但这并没有告诉我们论坛是否发生了变化。

command->command 看起来像一个 api 兼容适配器;在旧的 API 中,我们使用了 Forum.startDiscussion 命令。我们更改了模型,但继续支持旧命令以实现向后兼容性。它仍然会与请求同步。

这是真的(我们希望设计支持对模型进行积极更新,而不需要 clients/consumers 不断更新),但它不适合您的流程。

解决此类问题的最佳方法是首先将聚合(实际上是整个系统)视为黑盒。看看 API.

Given a Forum (that is not closed),
When I send a StartedDiscussionCommand for that forum,
A new Discussion is started.

还有

Given a Forum that was closed
When I send a CreateDiscussionCommand for that forum,
An exception is raised

请注意,您建议的 API 技术性太强。在 'real life' 中,您不创建讨论,而是发起讨论。

这意味着论坛的状态参与了讨论的创建。所以理想情况下(当查看黑匣子时),这样的场景将在论坛聚合中实现,并应用一个代表讨论聚合创建事件的事件。这是假设其他因素要求论坛和讨论是两个不同的集合。

所以您并不是真的希望命令处理程序 return/send 命令,您希望该处理程序决定是否创建聚合。

遗憾的是,Axon 尚不支持此功能。目前,Axon 无法通过其常规 APIs.

应用属于另一个聚合的事件

但是,有一种方法可以完成它。在 Axon 3 中,您不必 apply 一个事件,您也可以直接将一个事件发布到事件总线(在事件溯源的情况下,这将是一个事件存储实现)。因此,要实现这一点,您可以直接发布包含 DiscussionCreatedEvent 的 DomainEventMessage。讨论的ID可以是任意UUID,事件序号为0,因为是讨论的创建事件。