【问题标题】:Communication between two aggregates in Axon FrameworkAxon Framework 中两个聚合之间的通信
【发布时间】:2021-02-05 17:51:52
【问题描述】:

我是 Axon 框架、CQRS 和 DDD 的新手。我被教导使用关系数据库创建简单的 CRUD 应用程序。因此,我首先专注于构建数据模型,而不是领域模型。 我想改变我的软件方法,使其更加务实并创建真实世界的应用程序。因此,我想使用 CQRS 模式和事件溯源。

我现在正在使用 Spring Boot 和 Axon Framework 开发一个库应用程序。基本要求之一是用户已借书。我有两个用于用户和书籍的聚合。

这是我的 BookAggregate:

    @Data
    @AllArgsConstructor @NoArgsConstructor
    @Aggregate
    public class BookAggregate {
    
        @AggregateIdentifier
        private UUID id;
        private String name;
        private String isbnNumber;
        private int amountOfCopies;
        private Author author;
        private Genre genre;
        private PublishingHouse publishingHouse;
        
        ...

这是我的用户聚合:

    @Data
    @AllArgsConstructor @NoArgsConstructor
    @Aggregate
    public class UserAggregate {
    
        @AggregateIdentifier
        private UUID id;
        private String firstName;
        private String lastName;
        private String email;
        private String password;
        private String confirmPassword;
        private Set<Role> roles;
        private Date birthDate;
        private String telNumber;
        private Address address;
    
        ...

我的问题是:我应该在这两个聚合之间创建一个中间聚合,比如 SQL JOIN 吗?在这个示例中,我想看看在 Axon 框架中实现两个聚合相互通信的类似用例。

【问题讨论】:

    标签: spring-boot domain-driven-design cqrs axon


    【解决方案1】:

    我应该在这两个聚合之间创建一个中间聚合吗 像 SQL JOIN 之类的东西?在这个例子中,我想看看如何 两个聚合相互通信的类似用例是 在 Axon 框架中实现。

    很好的问题。我将首先说不,然后尝试帮助您理解原因。

    让我们从为什么存在聚合开始。来自DDD reference

    聚合

    在具有复杂关联的模型中,很难保证对象更改的一致性。
    [...]

    因此: 将实体和值对象聚集到聚合中,并在每个周围定义边界。选择一个实体作为每个聚合的根,并允许外部对象仅保存对根的引用

    好的,所以使用聚合的原因是为了避免中介。

    由于您正在执行 CQRS(这意味着 DDD 和事件溯源),因此您有一个命令端和一个查询端(命令查询请求隔离)。如果您更熟悉 CRUD 风格的应用程序和关系数据库,那么您很可能关注的是查询端而不是命令端。

    让我们在 CQRS 中澄清一些事情:

    • 聚合仅与命令端相关
    • 聚合负责处理命令和发布事件
    • 聚合负责执行业务规则

    这意味着对于命令端: 命令被传递给聚合,聚合决定是否应该发布事件或拒绝命令。

    在查询方面: 对来自命令端的事件进行处理以构建查询模型(可能是您所说的数据模型)

    因此,我首先关注的是构建数据模型,而不是领域 型号

    在事件源系统中,您通常关注域模型和相关事件。查询模型可以完全基于领域模型中的事件构建。

    由于您还没有关注域模型,因此您不必关心聚合 :)

    再次回到你原来的问题:

    基本要求之一是用户已借书

    我应该在这两个聚合之间创建一个中间聚合吗 SQL JOIN 之类的东西?

    根据您的域,您已经有一个用户和图书聚合。您只有一个要求,即用户可以借书。现在我不是你领域的专家,但问问你自己:你为什么需要另一个聚合?这个新的聚合体将负责什么?如果允许用户借书,那么只有 Book.borrow(userId) 发布 BookBorrowed(bookId, userId) 事件会有什么问题?

    【讨论】:

    • 如何让 book 成为一个实体而不是一个集合?毕竟用户想搜索要借的书?我该怎么做?
    【解决方案2】:

    我非常非常喜欢@martingreber 之前给出的建议。请考虑这些因素,因为它们与您当前设置的概念必要性有关。

    从务实的角度来看,我还想补充几件事(尽管我不认为这些会完全形成答案)。

    首先,您的BookAggregateUserAggregate 包含相当多的状态。在进行 CQRS 时,我建议您的命令模型(阅读:聚合)只包含任务执行所需的状态。我的意思是,需要驻留在BookAggregateUserAggregate 中的唯一字段是用于在@CommandHandler 注释方法中执行验证的字段。其余的都可以走了,因为它没有被使用。此外,如果以后需要它,您可以稍后简单地添加另一个事件源处理程序。这就是事件溯源的美妙之处。当您真正开始需要它时,您可以从聚合事件流中已经存在的事件中添加状态。

    其次,面向聚合体间的通信。值得注意的是,正如 Martin 也指出的那样,聚合通常是命令模型。因此,它们处理命令和发布事件。 @EventSourcingHandler 带注释的方法可能表明您可以处理 any 事件,但实际上,聚合只会处理它自己发布的事件。这适用于那些您需要重新创建聚合状态的事件。

    现在,是事件将允许您需要的聚合间通信。由于聚合只使用它们自己的概念和状态,因此您将不得不引入另一个组件,该组件对已发布的事件做出反应以将命令分派给其他聚合。这个组件可以简单地是一个常规的事件处理组件(阅读:一个带有@EventHandler 注释函数的类)。如果您想要响应的流程有时间概念,是否需要状态来执行其任务并且与 N 个聚合进行通信?然后您可以使用Saga 为例。

    第三,也是最重要的,是决定你是否真的需要在你的系统中有两个不同的聚合。这就是为什么我为@martingerber 他的回答鼓掌。

    不过,这是我的两分钱。希望对你有所帮助。

    【讨论】:

    • 另外,我想提一下,如果您在命令端使用@EventHandler 以保持其他聚合最终一致,请不要忘记使用@DisallowReplay 注释您的组件。这将防止您的事件处理程序在重播时被执行。重播在查询方面很好,但是在这种情况下,您的事件处理程序具有“副作用”以保持其他聚合同步。重放时必须跳过这些副作用。
    猜你喜欢
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 2018-06-05
    • 2012-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多