我们先从去餐厅吃饭来辅助理解什么是事件驱动设计,我们从点菜到上菜通常涉及到以下角色:
不同角色的职责:
我们通过事件的角度来考虑整个流程:
通过以上案例,我们可以将事件驱动设计总结为对业务过程中所发生的事件进行抽象,通过抽象后的事件来考虑代码、应用架构、业务流程编排设计的一种思维。
在系统建设过程中通常会遇到两个不同领域耦合的老代码和基于系统规模和时间成本不得不采用妥协方案而导致的耦合,为了应对这种工程实践的现状,考虑可扩展、易维护、可拆分,我们在无法对其进行服务级别隔离时,可以考虑通过事件发布和监听的手段将需要协同处理业务的不同领域代码进行依赖分离。
实现事件发布监听的方式多种多样,比较常接触到的有以下几种:
基于 Java 内置观察者模式相关接口实现
Guava EventBus
Spring Application Event
现在 Java 后端开发主流都是基于 Spring 生态,从减少依赖和提升开发效率方面考虑选择了 Spring Application Event,当然 Guava EventBus 也非常不错,只不过 Guava 的版本管理做的不够好,经常冲突不断,所以我通常在项目里能不用就不用。
public class OrderDomainEvent<T> extends PayloadApplicationEvent<T> {
/**
* Create a new PayloadApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
* @param payload the payload object (never {@code null})
*/
public OrderDomainEvent(Object source, T payload) {
super(source, payload);
}
}
public enum OrderDomainEventSource {
/**
* 销售订单创建事件源
*/
SALE_ORDER_CREATE("ORDER-SERVICE:ORDER-CREATE-EVENT", "销售订单创建事件"),;
/**
* 事件原
*/
private final String source;
/**
* 事件原描述
*/
private final String desc;
OrderDomainEventSource(String source, String desc) {
this.source = source;
this.desc = desc;
}
public String getSource() {
return source;
}
public String getDesc() {
return desc;
}
}
public class OrderCreateEventPayload {
/**
* 订单编号
*/
private String orderNumber;
public String getOrderNumber() {
return orderNumber;
}
public void setOrderNumber(String orderNumber) {
this.orderNumber = orderNumber;
}
@Override
public String toString() {
return "OrderCreateEventPayload{" +
"orderNumber='" + orderNumber + '\'' +
'}';
}
}
通常在事件中携带信息设计可分两种:
在实践过程中我通常使用第二种方案,这样可以减少依赖。
@Component
public class OrderDomainEventPublisher implements ApplicationEventPublisherAware {
private static final LogUtil LOG_UTIL = LogUtil.getLogger(OrderDomainEventPublisher.class);
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 订单领域事件发布
* @param orderDomainEvent 订单领域事件
*/
public <T> void publishEvent(OrderDomainEvent<T> orderDomainEvent){
OrderDomainEventSource saleOrderDomainEventSource = (OrderDomainEventSource) orderDomainEvent.getSource();
LOG_UTIL.info("{} 订单领域事件发布,事件源:{}", DateUtil.date(orderDomainEvent.getTimestamp()), saleOrderDomainEventSource.getDesc());
applicationEventPublisher.publishEvent(orderDomainEvent);
}
}
@Component
public class OrderCreateEventListener1 implements ApplicationListener<OrderDomainEvent<OrderCreateEventPayload>> {
private static final LogUtil LOG_UTIL = LogUtil.getLogger(OrderCreateEventListener1.class);
@Override
public void onApplicationEvent(OrderDomainEvent<OrderCreateEventPayload> event) {
OrderDomainEventSource orderCancelEventSource = (OrderDomainEventSource) event.getSource();
LOG_UTIL.info("{} 收到订单领域事件,事件发生时间:{},事件源:{}", DateUtil.now(), DateUtil.date(event.getTimestamp()), orderCancelEventSource.getDesc());
}
}
@Component
public class OrderCreateEventListener2 implements ApplicationListener<OrderDomainEvent<OrderCreateEventPayload>> {
private static final LogUtil LOG_UTIL = LogUtil.getLogger(OrderCreateEventListener2.class);
@Override
public void onApplicationEvent(OrderDomainEvent<OrderCreateEventPayload> event) {
OrderDomainEventSource orderCancelEventSource = (OrderDomainEventSource) event.getSource();
LOG_UTIL.info("{} 收到订单领域事件,事件发生时间:{},事件源:{}", DateUtil.now(), DateUtil.date(event.getTimestamp()), orderCancelEventSource.getDesc());
}
}
@Component
public class OrderCreateEventListener3 implements ApplicationListener<OrderDomainEvent<OrderCreateEventPayload>> {
private static final LogUtil LOG_UTIL = LogUtil.getLogger(OrderCreateEventListener3.class);
@Override
public void onApplicationEvent(OrderDomainEvent<OrderCreateEventPayload> event) {
OrderDomainEventSource orderCancelEventSource = (OrderDomainEventSource) event.getSource();
LOG_UTIL.info("{} 收到订单领域事件,事件发生时间:{},事件源:{}", DateUtil.now(), DateUtil.date(event.getTimestamp()), orderCancelEventSource.getDesc());
}
}
在工程实践过程中通常面临两种情况:
从第一种情况来考虑,如果不对代码进行分离设计则会面临以下情况:
从第二种情况来考虑,如果不对代码进行分离设计则会面临以下情况:
从以上分析不难看出通过事件发布和监听手段对代码进行隔离的好处在于对无关依赖的隔离、对技术复杂度的隔离、方便后续拆分。