1. 程式人生 > 實用技巧 >領域設計:領域事件

領域設計:領域事件

本文探討如下問題:

  • 什麼是領域事件
  • 領域事件的用途
  • 何時使用領域事件
  • 基於Spring事件的實現

什麼是領域事件

EDA風格與Reactor模式一文中,我們從觀察者模式聊到了EDA架構風格,然後聊了Reactor架構模式,最後以redis為例聊了Event-driven programming程式設計模式。

這些內容都是技術層面的,其共性是通過事件來進行解耦,提高系統的效能、擴充套件性、伸縮性、可運維性和靈活性。

而領域事件顧名思義也是通過事件來進行解耦,只是它是設計層面的技術。用於解耦領域設計中的元件:

何時使用領域事件?

在《領域驅動設計》的第七章,舉了一個貨運的例子。其中有個HandlingEvent,這就是一個事件物件。當貨物被裝貨、卸貨、密封、存放以及其他活動時就會建立一個HandlingEvent。

在這個例子中,是由人在貨物被處理時,使用系統輸入了一條HandlingEvent記錄下來而已!而如果當發生了一個HandlingEvent後,需要後續的處理(比如傳送通知給相關人員),該怎麼辦呢?這就是領域事件所要解決的問題!

在《領域驅動設計》中並沒有提到領域事件,領域事件是其後出現的。

實際上,當出現類似如下關鍵詞彙時,都可以考慮使用領域事件:

  • 當......
  • 如果發生......
  • 當......的時候,請通知我
  • 發生......時

下面通過Spring的事件來實現上例中的具體流程。

Spring事件例項程式碼

  • 新建HandlingEvent類,繼承ApplicationEvent,表示這是一個事件
public class HandlingEvent extends ApplicationEvent {
 private String actionName;
 public VisitEvent(Object source,String actionName) {
 super(source);
 this.actionName = actionName;
 }
}
  • 編寫事件釋出類EventPublisher,實現ApplicationEventPublisherAware,用於注入ApplicationEventPublisher,通過publishEvent方法來發布事件
  • Spring預設是同步事件,如果需要非同步事件,需要新增EnableAsync註解
@Service@EnableAsyncpublic class EventPublisher implements ApplicationEventPublisherAware {
 private ApplicationEventPublisher publisher;
 @Override
 public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
 this.publisher = applicationEventPublisher;
 }
 public void publish(ApplicationEvent event) {
 publisher.publishEvent(event);
 }
}
  • 編寫監聽類,只需要在監聽方法上新增eventListener註解,引數為需要監聽的事件類即可
  • 如果需要非同步處理,新增Async註解
  • 可以通過Async的value屬性來選擇執行緒池。推薦選擇自定義執行緒池,預設執行緒池沒有限制執行緒數量,可能導致OOM
@Componentpublic class HandlingListener {
 @EventListener
 @Async("eventThread")
 public void processHandlingEvent(HandlingEvent event) {
 // 處理事件
 }
  • 呼叫EventPublisher的publish方法,即可進行事件的釋出
eventPublisher.publish(new HandlingEvent(this, "move"));

整體的流程如下:

  • EventPublisher新增HandlingEvent
  • HandlingListener監聽到了HandlingEvent,執行對應的方法

我們看下Spring中如何實現這個流程的。

Spring事件實現

  • Spring啟動時,EventListenerMethodProcessor會將標識了EventListener註解的方法新增到Context中。這裡就是HandlingListener中的processHandlingEvent方法
for (Method method : annotatedMethods.keySet()) {
 for (EventListenerFactory factory : factories) {
 if (factory.supportsMethod(method)) {
 Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
 ApplicationListener<?> applicationListener =
 factory.createApplicationListener(beanName, targetType, methodToUse);
 if (applicationListener instanceof ApplicationListenerMethodAdapter) {
 ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
 }
 context.addApplicationListener(applicationListener);
 break;
 }
 }
}
  • AbstractApplicationContext實現了ApplicationContext介面,其中維護了掃描到的ApplicationListener
@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {
 Assert.notNull(listener, "ApplicationListener must not be null");
 if (this.applicationEventMulticaster != null) {
 this.applicationEventMulticaster.addApplicationListener(listener);
 }
 this.applicationListeners.add(listener);
}
  • 在EventPublisher釋出事件時,觸發ApplicationEventPublisher的publishEvent方法
  • ApplicationContext介面繼承了ApplicationEventPublisher介面,最終觸發AbstractApplicationContext的publishEvent方法,將事件廣播出去

ApplicationContext介面為什麼要繼承ApplicationEventPublisher介面?

因為Spring中定義了幾個預設的事件ContextRefreshedEvent,ContextStartedEvent,ContextStoppedEvent,ContextClosedEvent,RequestHandledEvent,而ApplicationContext就是這些事件的布者。

protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
 ......
 else {
 getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType); // 廣播事件
 }
 .....
}
  • 最終觸發SimpleApplicationEventMulticaster的multicastEvent方法
@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
 ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
 Executor executor = getTaskExecutor();
 if (executor != null) {
 executor.execute(() -> invokeListener(listener, event));
 }
 else {
 invokeListener(listener, event);
 }
 }
}
  • 獲取ApplicationListener,然後使用Executor去執行listener

參考資料

  • Spring原始碼
  • 《領域驅動設計:軟體核心複雜性應對之道》
  • 《實現領域驅動設計》