为什么需要在聚合对象中使用EventSourcingHandler?

7

提醒:我并不知道我在做什么,所以即使是问这个问题也可能会出错。

我想要更新一个简单对象(聚合)的状态,并且向UI提供更改后的对象投影。这是我的聚合对象(命令处理程序存在,但此处未显示)。

@Aggregate
public class Widget {
    @AggregateIdentifier
    private String id;
    private String color;
...
    @EventSourcingHandler
    public void on(ChangeColorEvt evt) {
        color = evt.getColor();
    }
... 
}

...这是我的预测:

public class WidgetProjection {
    private final EntityManager entityManager;
    private final QueryUpdateEmitter queryUpdateEmitter;
...
    @EventHandler
    public void on(ChangeColorEvt evt) {
        ProjectedWidget projection = entityManager.find(ProjectedWidget.class, evt.getId());
        projection.setColor(evt.getColor());
        queryUpdateEmitter.emit(FetchWidget.class, query -> evt.getId().startsWith(query.getFilter().getIdStartsWith()), projection);
    }
...
}

QueryHandler的作用是寻找实例并将其返回,符合预期。

以下是我的问题:

  1. 为什么我需要在聚合中使用EventSourcingHandler?它似乎执行了工作,但结果没有被存储或发送到任何地方。
  2. 那么,接下来的问题是:在执行EventSourcingHandler之后,Widget的实例(而不是projection)是否被存储、查看或发送到任何地方?
  3. 如果确实需要EventSourcingHandler,有没有办法避免在两个独立的业务逻辑副本(一个在EventSourcingHandler中,一个在EventHandler中)中更新相同的业务逻辑?我非常讨厌在两个地方更新相同的业务逻辑的想法。

感谢大家提供的任何澄清。感谢您的帮助!


@Allard,我希望你或者你团队的某个人能看到这条信息。非常感谢任何帮助。谢谢。 - Jonathan M
1
我可以保证,他的团队中肯定有人看到了它。希望我能为你澄清一些事情,@Jonathan M :-) - Steven
1个回答

10
希望我能对这个问题有所帮助!
简短的回答是:是否需要使用 @EventSourcingHandler 取决于你想如何建模应用程序。
现在,这并不说太多,让我稍微解释一下为什么我这样说。 回答你的问题时,我假设你想创建一个遵循DDD、CQRS和事件溯源思想的应用程序。这些是Axon在构建应用程序时试图为您简化的概念。
让我来回答你的编号问题:
你需要在聚合根中使用@EventSourcingHandler注释的函数的原因是,(1)基于(2)它发布的事件更新聚合根状态。为了回答第一点,为什么要更新聚合根的状态?因为聚合根是应用程序的命令模型,它负责根据接收到的命令做出决策。为了能够做出决策,有时候聚合根需要状态,但有时候不需要。因此,在你分享的Widget示例中,我认为color字段根本没有用于驱动后续业务逻辑,因此你可以完全省略这个状态而不会有任何问题。在我的回答的第二点中,我试图指出聚合根只会处理来自自身的事件。这是因为事件是聚合根的来源,因为事件构成了在给定模型上发生的所有增量。
你的后续问题很好地配合了我在第一点中开始的解释。答案很简单,你的Widget聚合根没有被存储在任何地方,不是“原样”的存储。每次从Repository(在Axon中自动完成)检索聚合根时,默认情况下会从事件存储库中检索该给定聚合根的所有事件。然后,创建一个的聚合根实例,并且框架将重放找到的该聚合根实例的所有事件。因此,每当有新命令进入时,你实际上都在对聚合根进行事件溯源。这可能听起来有点过度,因为给定聚合根的事件数量可能会增长到相当大的集合。可以通过制作snapshots等方式解决这个问题。
如果你将应用程序拆分为专门处理业务逻辑(命令模型)和简单返回查询模型作为答案(查询模型)的部分,则可以决定使用State Stored Aggregate。因此,请注意,使用Axon时不需要完全进行事件溯源;这只是框架的默认操作方式。因此,我理解你的担忧,即你正在复制你的逻辑。但是,你可以严格分离做出所有决策的部分并将其保存在聚合根中。
查询模型(在你的示例中为ProjectedWidget)可以以任何格式和任何数据库/工具存储,最好没有任何业务逻辑。
如果你发现自己在应用程序的查询侧添加业务逻辑,这可能表明你应该将其升级为从聚合根发出的事件。

希望这篇文章能为您提供一些有关为什么要选择事件溯源的见解。该文章比我在这里所做的描述更详细地介绍了CQRS,而该链接则是有关事件溯源的,希望它们能为您提供比我刚才所说的更全面的解释。


然而,在需要 ESH 的情况下,EH 的状态变化基本上与其相同。如果状态变化涉及多个具有计算的属性,则存在出错的机会:也许在 ESH 中编码的计算是正确的,但在 EH 中却不是。我们已经创造了一个糟糕的局面,因为我们基本上有相同的计算,但在两个地方。有没有办法避免这种情况? - Jonathan M
嗨,乔纳森,避免这种情况的最佳方法是在命令模型中做出重要决策和计算,只在视图模型中留下琐碎的计算。但要注意不要过度。建模是一个精细的平衡行为... - Allard
@JonathanM 如果你需要避免在特定领域的计算中出现重复,我认为你可以将它们放在一个领域服务中。从我的角度来看,我会让该服务接收所有所需的计算字段,并每次返回相同的结果;甚至可以将其作为抽象类,并使用静态函数进行计算。 - Steven
@Allard,啊,我想你是说把繁重的计算放在CH中,并将结果传递给ESH / EH,以便这些处理程序可以更新状态,对吗? - Jonathan M
1
没错,@eriegz!而且,谢谢你通知我这件事。我刚刚调整了我的回复中的链接,指向正确的页面。 - Steven
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接