領域設計:領域事件
阿新 • • 發佈:2020-12-10
本文探討如下問題:
- 什麼是領域事件
- 領域事件的用途
- 何時使用領域事件
- 基於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原始碼
- 《領域驅動設計:軟體核心複雜性應對之道》
- 《實現領域驅動設計》