4

在 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

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

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

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

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

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

4

2 回答 2

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发布 Event,您也可以直接将其发布到 Event Bus(在 Event Sourcing 的情况下将是 Event Store 实现)。因此,要实现这一点,您可以直接发布包含 DiscussionCreatedEvent 的 DomainEventMessage。讨论的ID可以是任何UUID,事件的序号为0,因为它是讨论的创建事件。

于 2017-02-01T07:57:41.460 回答
-1

关于应该首选哪种做法的任何想法?

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

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

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

人们一定会问,为什么要参与论坛?

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

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

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

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

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

于 2017-02-01T07:04:11.650 回答