使用 Spring Integration EventBus 发布领域事件

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

如果您不认真对待它们,集成可能会变得混乱——尤其是当您在同一点同时进行入站和出站集成时。如果您没有首先奠定适当的基础,那么集成问题可能会不情愿地污染您的领域。 六边形架构 通过将它们移交给应用程序服务层,帮助您的域免受安全、事务和其他非功能方面的影响。但是在应用程序服务层之外仍然存在我们必须解决的集成挑战——特别是那些源自应用程序域发布的 域事件 的外部集成。

如果您已经在项目中使用 spring 框架,那么拥有 spring 集成层可以大大简化这些挑战。尽管 apache camel 也是一个不错的选择,但我发现 spring integration 更具吸引力,因为它的简单性和与其他 spring 项目的兼容性。

在这篇文章中,我将演示我如何在我最近的一个项目中使用 spring 集成来保持域不关心集成问题并同时进行简化。该项目的主要目标是加快乘客在非常繁忙的机场出境的过程。该项目与属于航空公司、机场、居住和外交部门等的外部系统进行了大量的入站和出站集成。下图描述了该项目的软件应用程序架构。

在图的左侧,rest api 层中的这些小块表示负责操作底层域对象状态的工作流活动。 rest api 层通过这些工作流活动与应用程序 api 通信。这还涉及资源对象和域对象之间必要的来回转换。左侧显示的不同类型的请求来自前端应用程序。不需要任何外部输入的请求通过工作流活动直接进入应用程序服务。然而,一些需要外部系统干预的请求会触发集成流程以从外部世界收集必要的信息,然后将丰富的请求发送到应用程序服务 api 层。通过与由 spring 集成提供的 消息传递网关 支持的 java 接口交互,可以大大简化这些外部集成。左下角的 rest api 请求终止于工作流活动,该活动在调用应用程序服务之前触发与外部系统的集成流。

而领域事件(显示为从领域向外指向的虚线箭头)是由领域发布的,影响某些业务事件。这些事件被发布到外部世界,因此他们也可以通过订阅这些事件来对它们做出反应。你会很幸运,如果所有其他应用程序都在下游并且解释由你的 限界上下文 生成的事件消息是他们的责任。因此,在大多数情况下,您有责任将消息构造成各自的格式并将它们放入某个消息传递系统,以便他们可以从中进行选择。不仅如此,您有时可能需要为单个事件通知多个系统。这需要转换、服务编排、路由和许多其他需要关心的集成问题。另外,不要忘记您还需要将所有领域事件作为快照保存到某个事件存储中以供将来参考。

为了解决这些问题,我们将实现一个自定义事件总线,我们将使用它来发布来自应用程序域的事件。然后我们能够将这些事件直接消费到 spring 集成流程中,就像上图所示,以通知外部系统为它们各自的格式。在那里,您将可以完全自由地在 spring 集成流程中使用 eip 模式 解决集成问题。

下面是将用于发布和订阅事件的事件总线的基本接口。完整的源代码可在 github 上获得。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

在幕后,这个事件总线实现使用发布-订阅-通道将事件发送给所有订阅者。事件总线订阅/取消订阅方法中的第一个参数应该是该发布-订阅-通道的名称。而对于发布领域事件,事件总线将能够从事件类型中提取它。下面的代码片段显示了如何定义域事件:


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

此处“domaineventschannel”是必须为事件总线配置的发布-订阅-通道的名称,如上所述。下面是将所有必要组件连接在一起的示例弹簧配置。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

domaineventpublisher 是一个方便的类,它将调用委托给事件总线以发布事件。它是 环境上下文 模式的实现,可帮助您从领域对象层次结构深处的任何地方发布事件。如果您不能接受使用该模式的权衡,您可以根据自己的喜好提供对事件总线的访问。其余的配置是很解释性的。因此,让我们转到 foo-bar-integration-flow.xml。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

在上面的集成流程中,我们将域事件路由到两个不同的通道,一个在控制台打印出 barevent,另一个调用带有 subsribeevent 注释的应用程序服务方法,如下面的代码片段所示。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

虽然上面的集成流程没有发生太多事情,但足以说明该工具的强大之处。您现在可以完全自由地以任何您想要的方式路由、转换和发布事件。

如果您发现上述处理事件的方式在通知订阅者 bean 方面有点冗长,那么您可以使用以下配置使其更简洁。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

在上面的配置中,“eventsubscriberbeanprocessor”会自动注册所有具有使用 subscribeevent 注释进行注释的方法的 bean。因此,您不必总是像我们为“eventstore”和“foobarapplicationservice”所做的那样显式注册它们。尽管这将比以前的配置更具优势,但请确保您不应该违反 聚合 的事务边界约束。其中指出,在一个事务中只能修改一个聚合。最后,下面的测试用例揭示了域事件是如何发布的。


 package org.springframework.integration.eventbus;

public interface eventbus{ public boolean publish(event event); boolean subscribe(string topic, object subscriber); boolean unsubscribe(string topic, object subscriber); }

请注意我是如何使用 domaineventpublisher 来发布事件的。而当调用 updatefoo 和 updatebar 方法时,其余事件将从域内部发布。

今天就是这样。从 github 上 获取源代码并使用它并用你的反馈启发我。