一. 应用场景
假如你是订单服务的一名研发,正在开发支付成功这个业务功能,在深度学习 DDD 后,你写出了一组漂亮的代码。
@Transactional
public void paySuccess(Long orderId){
// 1. 获取并验证订单聚合根有效性
Order order = this.orderRepository.getById(orderId);
if (order == null){
throw new IllegalArgumentException("订单不存在");
}
// 2. 修改价格
order.paySuccess();
// 3. 保存 Order 聚合
this.orderRepository.save(order);
// 4. 通知物流服务进行发货
}
成功上线后,系统运行稳定。随后,你又陆续接到更多需求,比如:
更多的需求还在路上,此时原本漂亮的代码已经逐渐失控,变得有些面目全非:
@Transactional
public void paySuccess(Long orderId){
// 1. 获取并验证订单聚合根有效性
Order order = this.orderRepository.getById(orderId);
if (order == null){
throw new IllegalArgumentException("订单不存在");
}
// 2. 修改价格
order.paySuccess();
// 3. 保存 Order 聚合
this.orderRepository.save(order);
// 4. 通知物流服务进行发货
// 5. 为用户发生触达短信
// 发送触达短信逻辑
// 6. 清理购物车
// 7. 使用优惠券,更新优惠券状态
// 8. 提交风控管理
// 其他代码
}
一些问题慢慢的浮现出来:
前期这些问题你可能并不在意,直到有一天出现线上问题:
聪明的你为了避免别人的服务到影响自己,悄悄的在每个业务调用时增加了 try-catch,但腐化仍旧在延续……
如果你也意识到这个问题,那正是引入领域事件的好时机。
二. 领域事件
领域事件是领域模型的重要组成部分,用于表示在领域中发生的一些重要的业务事情或者状态变化,它用来捕获领域中的一些变更,记录事件发生时的业务状态,并将这些数据传输到订阅方,以开展后续业务操作。
领域事件有以下一些特点:
领域事件分为内部领域事件和外部领域事件,想搞清楚两者的区别,需要先回顾下“六边形架构”:
图片
2.1. 内部领域事件
内部领域事件的主要目标是在领域间传播信息,以实现业务逻辑的分离和职责隔离。
内部领域事件通常使用同步或异步的方式在内存中传播。例如,在Java Spring中,可以使用ApplicationEventPublisher和@EventListener实现同步或异步的内部领域事件,这些事件不会跨服务或应用传播。
内部领域事件工作在内存中,在设计时需要注意以下几点:
Spring Event 是内部领域事件落地的一把利器,稍后进行详解。
2.2. 外部领域事件
外部领域事件的主要目标是在跨服务或子域实现分布式的业务逻辑和系统间解耦。
外部领域事件通常使用消息队列(如Rocketmq、Kafka等)实现异步的跨服务传播。
外部领域事件工作在消息中间件之上,在设计时需要注意以下几点:
消息中间件是 外部领域事件 落地的关键技术,由于篇幅原因,在此不做过多解释。稍后会有文章进行详解。
三. Spring Event 机制
Spring Event 是 Spring Framework 中的一个模块,帮助在应用程序中实现事件驱动。它主要用于组件之间同步/异步通信,解耦事件发布者和事件消费者。
使用 Spring Event 包括以下步骤:
在 Spring 中,事件的处理器可以通过三种方式来实现:
3.1. 基于接口的事件处理
由于与 Spring 存在强耦合,现在已经很少使用,可以直接跳过。
下面是一个基于接口的事件处理的示例代码:
@Component
public class MyEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(MyEvent event) {
// 处理事件
System.out.println("Received event: " + event.getMessage());
}
}
public class MyEvent {
private String message;
public MyEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
@Component
public class MyEventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishEvent(String message) {
MyEvent event = new MyEvent(message);
eventPublisher.publishEvent(event);
}
}
在这个示例中,MyEvent 是一个自定义的事件类,MyEventListener 是一个实现了 ApplicationListener 接口的监听器,用于处理 MyEvent 事件,MyEventPublisher 是用于发布事件的类。
当应用程序调用 MyEventPublisher 的 publishEvent 方法时,会触发一个 MyEvent 事件,MyEventListener 中的 onApplicationEvent 方法将被自动调用,从而处理这个事件。
3.2. 基于注解的事件处理
Spring 提供 @EventListener 和 @TransactionListener 两个注解以简化对事件的处理。
3.2.1. @EventListener
Spring 的 EventListener 监听器是一种相对于传统的事件监听方式更为简洁和灵活的事件机制。与传统的事件机制不同,EventListener 不需要显示地继承特定的事件接口,而是使用注解标识需要监听的事件类型,然后通过一个单独的监听器类处理所有类型的事件。
相比之下 EventListener 的优势主要有以下几点:
以下是一个简单的例子:
@Component
public class MyEventListener{
@EventListener
public void onApplicationEvent(MyEvent event) {
// 处理事件
System.out.println("Received event: " + event.getMessage());
}
}
public class MyEvent {
private String message;
public MyEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
@Component
public class MyEventPublisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void publishEvent(String message) {
MyEvent event = new MyEvent(message);
eventPublisher.publishEvent(event);
}
}
相比基于接口的事件处理,EventListener 是一种更加简洁、灵活、松耦合、可测试的事件机制,能够有效地降低开发的复杂度,提高开发效率。
3.2.2. @TransactionEventListener
在 Spring 中,TransactionEventListner 和 EventListner 都是用于处理事件的接口。不同之处在于
具体来说,在使用 Spring 的声明式事务时,可以在事务提交后触发某些事件。这就是 TransactionEventListner 的应用场景。而 EventListner 则不涉及事务,可以用于在事件发布后触发一些操作。
下面是一个简单的示例,演示了如何使用 TransactionEventListner 和 EventListner:
@Component
public class MyEventListener {
@EventListener
public void handleMyEvent(MyEvent event) {
// 处理 MyEvent
}
@TransactionalEventListener
public void handleMyTransactionalEvent(MyTransactionalEvent event) {
// 处理 MyTransactionalEvent
}
}
@Service
public class MyService {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Autowired
private MyRepository myRepository;
@Transactional
public void doSomething() {
// 做一些事情
MyEntity entity = myRepository.findById(1L);
// 发布事件
eventPublisher.publishEvent(new MyEvent(this, entity));
// 发布事务事件
eventPublisher.publishEvent(new MyTransactionalEvent(this, entity));
}
}
在这个例子中,MyEventListener 类定义了两个方法,handleMyEvent 和 handleMyTransactionalEvent,分别处理 MyEvent 和 MyTransactionalEvent 事件。其中,handleMyTransactionalEvent 方法用 @TransactionalEventListener 注解标记,表示它只会在事务提交后触发。
MyService 类中的 doSomething 方法使用 ApplicationEventPublisher 来发布事件。注意,它发布了两种不同类型的事件:MyEvent 和 MyTransactionalEvent。这两个事件会分别触发 MyEventListener 中的对应方法。
总的来说,Spring 的事件机制非常灵活,可以方便地扩展应用程序的功能。TransactionEventListner 和 EventListner 这两个接口的应用场景有所不同,可以根据实际需求选择使用。
3.3.基于异步事件处理
@Async是Spring框架中的一个注解,用于将一个方法标记为异步执行。使用该注解,Spring将自动为该方法创建一个新线程,使其在后台异步执行,不会阻塞主线程的执行。
在实际应用中,使用@Async可以大大提升应用的并发处理能力,使得系统能够更快地响应用户请求,提高系统的吞吐量。
@Async 和 @EventListener 或 @TransactionEventListener 注解在一起使用时,会产生异步的事件处理器。使用这种组合的方式,事件处理器会在单独的线程池中执行,以避免阻塞主线程。这种方式在需要处理大量事件或者事件处理器耗时较长的情况下非常有用,可以有效提升应用的性能和可伸缩性。同时,Spring 框架对这种方式也提供了完善的支持,可以方便地使用这种方式来实现异步事件处理。
下面是一个简单的示例代码,演示了如何在 Spring 中使用 @Async 和 @EventListener 一起实现异步事件处理:
@Component
public class ExampleEventListener {
@Async
@EventListener
public void handleExampleEvent(ExampleEvent event) {
// 在新的线程中执行异步逻辑
// ...
}
}
在这个示例中,ExampleEventListener 类中的 handleExampleEvent 方法使用了 @Async 和 @EventListener 注解,表示这个方法是一个异步事件监听器。当一个 ExampleEvent 事件被触发时,这个方法会被异步地执行。在这个方法中,可以执行任何异步的逻辑处理,比如向队列发送消息、调用其他服务等。
备注:在使用 @Async 时,需要根据业务场景对线程池进行自定义,以免出现资源不够的情况(Spring 默认使用单线程处理@Async异步任务)
四. Spring Event 应用场景分析
综上所述,当领域事件发出来之后,不同的注解会产生不同的行为,简单汇总如下:
@EventListener |
@TransactionEventListener |
|
无 @Async |
顺序、同步执行 |
事务提交后、同步执行 |
有 @Async |
顺序、异步执行 |
事务提交后、异步执行 |
4.1. @EventListener
图片
特点:
应用场景:
4.2. @TransactionEventListener
图片
特点:
应用场景:
备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效
4.3. @EventListener + @Async
图片
特点:
应用场景:
4.4. @TransactionEventListener + @Async
图片
特点:
应用场景:异步处理。记录操作日志,异步保存数据等
备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效
五. 小结
领域事件是系统中的解耦利器,包括:
Spring Event 是实现内部领域事件解耦的利器,基于 事件监听注解 和 同步/异步 两组注解的组合为不同的应用场景提供不同的支持。
@EventListener |
@TransactionEventListener |
|
无 @Async |
顺序、同步执行 |
事务提交后、同步执行 |
有 @Async |
顺序、异步执行 |
事务提交后、异步执行 |
外部领域事件 强依赖于消息中间件的使用,稍后会有文章进行详解。