当前位置: 首页 > 科技观察

SpringEvent玩DDD域事件

时间:2023-03-12 06:01:51 科技观察

1。领域事件领域事件是DDD中的重要模式之一。它们主要用于模型或系统之间的解耦,以提高系统的可扩展性和可维护性。(1)什么是领域事件?领域事件是领域驱动设计(简称DDD)中的一个重要概念。它特指领域模型中发生的有意义的事件,是领域模型中重要的业务动作对执行结果的抽象,比如订单创建、支付完成等。在DDD中,领域事件是一种传递信息的机制,它使不同领域模型之间的通信更容易和更灵活。通过将事件分发给相关的订阅者,可以实现不同领域模型之间的松散耦合,从而更容易扩展和维护应用程序。领域事件通常由领域对象主动触发和发布,事件处理器负责订阅和处理事件。通过事件发布和订阅机制,可以在应用中实现高效的事件驱动架构,从而更好地支持复杂的业务逻辑和业务流程。说起来有点抽象,举个简单的例子:假设有一个电商系统,用户下单后,需要生成订单,并通知相关人员。在领域模型中,可以定义一个Order领域对象,它可以包含多个属性,如订单号、下单时间、购买的产品信息、送货地址等。当用户下订单时,可以通过调用Order对象的方法生成订单,也可以通过领域事件发送通知。具体来说,可以定义一个OrderCreated字段事件来表示订单创建的事件。该事件包含一些必要的属性,如订单号、下单时间、购买的产品信息、送货地址等。创建Order对象后,可以通过领域事件触发发送通知的操作,比如发送邮件或短信通知相关人员。(2)领域事件的应用场景领域事件的应用有很多。从图中可以看出领域事件可以:保证聚合之间的数据一致性。当对一个聚合根的操作触发对其他聚合根的更改时,这些更改被发布为域事件,其他聚合根可以订阅这些事件并更新自己的状态,从而实现最终一致性。替换批处理。可以作为定时任务、异步任务等任务的触发器,避免定时+扫描等批处理。实施事件溯源模式。存储所有领域事件,可用于恢复聚合的状态,实现事件源模式;也可用于后续的审计和调试。执行限界上下文集成。将事件从一个子域发布到另一个子域允许两个子域从彼此的存在中解耦。领域事件虽然好,但还是需要技术框架的支持。其实Spring的Event机制足以满足各种需求。2、Spring对Event的支持在Spring中,事件处理可以通过三种方式实现:基于接口的事件处理:通过实现ApplicationListener接口,重写onApplicationEvent方法来处理事件。基于注解的事件处理:通过在方法上添加@EventListener或@TransactionEventListener注解来处理事件,可以指定事件的类型、监听的条件等基于异步事件处理:通过使用@Async注解来处理事件异步,可以提高应用程序的响应能力。(1)由于与Spring耦合性强,现在很少使用基于接口的事件处理,可以直接跳过。以下是基于接口的事件处理的示例代码:@ComponentpublicclassMyEventListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(MyEventevent){//处理事件System.out.println("Receivedevent:"+event.getMessage());}}publicclassMyEvent{私有字符串消息;publicMyEvent(Stringmessage){this.message=message;}publicStringgetMessage(){返回消息;}}@ComponentpublicclassMyEventPublisher{@AutowiredprivateApplicationEventPublishereventPublisher;publicvoidpublishEvent(Stringmessage){MyEventevent=newMyEvent(message);eventPublisher.publishEvent(事件);}}本例中,MyEvent是自定义事件类,MyEventListener是实现了ApplicationListener接口的监听器,用于处理MyEvent事件,MyEventPublisher是用于发布事件的类。当应用调用MyEventPublisher的publishEvent方法时,会触发一个MyEvent事件,会自动调用MyEventListener中的onApplicationEvent方法来处理这个事件。(2)基于注解的事件处理Spring提供了@EventListener和@TransactionListener两个注解来简化事件处理。@EventListenerSpring的EventListener监听器是一种比传统的事件监听方式更加简洁灵活的事件机制。与传统的事件机制不同,EventListener不需要显式继承特定的事件接口,而是使用注解来标识需要监听的事件类型,然后通过单个监听器类来处理所有类型的事件。相比之下,EventListener的主要优点如下:更灵活:EventListener不依赖于任何特定的事件接口,这使得事件处理更加灵活,可以监听和处理任何类型的事件。更简洁:与传统的事件监听方式相比,使用EventListener可以避免一系列繁琐的接口定义和实现,简化代码结构,让开发更高效。更松耦合:EventListener将事件发布者和事件处理器分离,遵循松耦合的设计原则,提高了代码的可维护性和可扩展性。更可测试:由于EventListener可以监听和处理任何类型的事件,因此可以通过单元测试来验证其功能是否正确,从而提高测试的可靠性。下面是一个简单的例子:@ComponentpublicclassMyEventListener{@EventListenerpublicvoidonApplicationEvent(MyEventevent){//处理事件System.out.println("Receivedevent:"+event.getMessage());}}publicclassMyEvent{私有字符串消息;publicMyEvent(Stringmessage){这个。消息=消息;}publicStringgetMessage(){返回消息;}}@ComponentpublicclassMyEventPublisher{@AutowiredprivateApplicationEventPublishereventPublisher;publicvoidpublishEvent(StringEmessage){event=newMyEvent(message);eventPublisher.publishEvent(事件);}}与基于接口的事件处理相比,EventListener是一种更加简洁、灵活、松耦合、可测试的事件机制,可以有效降低开发复杂度,提高开发效率。@TransactionEventListener在Spring中,TransactionEventListner和EventListner都是处理事件的接口。不同的是TransactionEventListner是在事务提交后触发,而EventListner是在事件发布后触发。具体来说,当使用Spring的声明式事务时,可以在事务提交后触发某些事件。这就是TransactionEventListner发挥作用的地方。EventListner不涉及事务,可以用来在事件发布后触发一些操作。这是一个演示如何使用TransactionEventListner和EventListner的简单示例:类MyService{@AutowiredprivateApplicationEventPublishereventPublisher;@AutowiredprivateMyRepositorymyRepository;@TransactionalpublicvoiddoSomething(){//做一些事情MyEntityentity=myRepository.findById(1L);//发布事件eventPublisher.publishEvent(MyEvent(this,entity));//发布交易事件eventPublisher.publishEvent(newMyTransactionalEvent(this,entity));}}在这个例子中,MyEventListener类定义了两个方法,handleMyEvent和handleMyTransactionalEvent,分别处理MyEvent和MyTransactionalEvent事件。其中,handleMyTransactionalEvent方法被@TransactionalEventListener注解标记,表示只有事务提交后才会触发。MyService类中的doSomething方法使用ApplicationEventPublisher来发布事件。请注意,它发布两种不同类型的事件:MyEvent和MyTransactionalEvent。这两个事件会分别触发MyEventListener中相应的方法。总的来说,Spring的事件机制非常灵活,可以方便地扩展应用程序的功能。TransactionEventListner和EventListner的应用场景不同,可以根据实际需要选择使用。(3)基于异步事件处理@Async是Spring框架中的一个注解,用来标记一个方法是异步执行的。使用这个注解,Spring会自动为方法创建一个新的线程在后台异步执行,不会阻塞主线程的执行。在具体应用中,使用@Async可以大大提高应用的并发处理能力,使系统能够更快地响应用户请求,提高系统吞吐量。当@Async和@EventListener或@TransactionEventListener注解一起使用时,会产生一个异步事件处理器。通过这种组合,事件处理程序在单独的线程池中执行,以避免阻塞主线程。当需要处理大量事件或事件处理器耗时较长时,这种方法非常有用,可以有效提高应用程序的性能和可扩展性。同时,Spring框架也对该方法提供了完善的支持,可以很方便的用来实现异步事件处理。下面是一个简单的示例代码,演示了如何在Spring中结合使用@Async和@EventListener来实现异步事件处理:异步逻辑//...}}在此示例中,ExampleEventListener类中的handleExampleEvent方法使用@Async和@EventListener注解来指示该方法是一个异步事件侦听器。当触发ExampleEvent事件时,将异步执行此方法。在这个方法中,你可以进行任何异步逻辑处理,比如发送消息到队列,调用其他服务等等。备注:使用@Async时,需要根据业务场景自定义线程池,避免资源不足(Spring默认使用单线程处理@Async异步任务)4.场景分析综上所述,当领域事件发生后结果出来了,不同的注解会产生不同的行为。简单总结如下:@EventListener@TransactionEventListener没有@Async顺序,事务提交同步执行后,同步执行有@Async顺序,事务提交异步执行后,异步执行(1)@EventListener特点:顺序执行。调用publish(Event)会自动触发同步执行对@EventListner注释方法的调用。使用主线程执行,方法抛出的异常会中断调用环节,触发事务的回归应用场景:事务消息表。在同一个事务中完成业务数据和消息表修改的业务验证。最后一次验证业务对象。如果校验不通过,直接抛出异常中断数据库事务业务插件。在当前线程和事务中执行插件,完成业务扩展(二)@TransactionEventListener特点:事务提交后执行。调用publishing(Event)时,回调只是在上下文中注册,不会立即执行;只有事务提交后,才会触发调用@TransactionEventListner注解的方法同步执行。使用主线程执行,方法抛出的异常会中断调用环节,此时事务不会返回(事务已经提交,没有办法返回)应用场景:数据同步。事务提交后,变更同步到ES或Cache记录审计日志。仅在业务变更成功更新到数据库时记录注意:@TransactionEventLister必须在事务上下文中,脱离上下文,调用不会生效(3)@EventListener+@Async特性:顺序执行。调用publish(Event)会自动触发对@EventListner注释方法的调用以异步执行。使用独立的线程池执行任务,方法抛出的异常对主进程没有影响应用场景:记录详细日志,协助排错(4)@TransactionEventListener+@Async特点:事务提交后执行。调用publishing(Event)时,回调只是在上下文中注册,不会立即执行;只有事务提交后,才会触发调用@TransactionEventListner注解方法的异步执行。使用独立的线程池执行任务,方法抛出的异常对主进程没有影响应用场景:异步处理。记录操作日志,异步保存数据等。备注:@TransactionEventList必须在事务上下文中,脱离上下文,调用不会生效5.总结字段事件的实现不仅需要强大的设计能力,还需要配套的基础设施.Spring作为最常用的框架,实现了一套完整的基于发布和订阅的事件管理机制。工具是否在手,能否根据业务场景选择合适的方案,成为了研发的责任。简单想想下面的组合适合做什么:@EventListener@TransactionEventListener@EventListener+@Async@TransactionEventListener+@Async