Aggregate Root工厂方法可以返回命令而不是发布事件吗?

时间:2017-02-01 04:06:40

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

在Vaughn Vernon的Implementing Domain-Driven Design书中,他描述了在聚合根中使用工厂方法。一个例子是Forum聚合根,其startDiscussion工厂方法返回了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

我们点击StartDiscussionCommand来处理ForumForum然后发布DiscussionStartedEvent。外部事件处理程序将捕获DiscussionStartedEvent,转换它并触发CreateDiscussionCommand。另一个处理程序将使用Discussion实例化CreateDiscussionCommand,而Discussion会触发DiscussionCreatedEvent

或者,我们可以改为: StartDiscussionCommand - > CreateDiscussionCommand - > DiscussionCreatedEvent

我们触发StartDiscussionCommand,它将触发命令处理程序并调用将返回Forum的{​​{1}}的startDiscussion()方法。然后处理程序将调度此CreateDiscussionCommand。另一个处理程序接收该命令并使用它来实例化CreateDiscussionCommand。然后,Discussion会触发Discussion

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

有关哪种做法应该首选的任何想法?还是有另一种方法可以做到这一点吗?

2 个答案:

答案 0 :(得分:4)

解决此类问题的最佳方法是首先将您的聚合(实际上是整个系统)视为黑盒子。只需看看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太技术化了。在“现实生活”中,你不会创建一个讨论,而是开始讨论。

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

因此,您并不希望Command处理程序返回/发送命令,您希望该处理程序决定是否创建聚合。

不幸的是,Axon还不支持此功能。目前,Axon无法通过常规API应用属于另一个聚合的事件。

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

答案 1 :(得分:-1)

  

有关哪种做法应该首选的想法?

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

也就是说,如果您的流程是

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

有人会问为什么论坛会参与其中?

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

这是一种幻觉 - 我们在过去的某个任意点处查看论坛的状态来处理讨论命令。换句话说,在此检查之后,论坛的状态可能会发生变化,我们在讨论中的处理将无法知晓。因此,在验证命令时或通过在Discussion中检查读取模型来进行此检查是正确的。

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

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

这是真实的事情(我们希望设计支持对模型的积极更新,而不要求客户/消费者不断更新),但它不适合您的流程。