1. 領(lǐng)域事件
領(lǐng)域事件是 DDD 中重要的模式之一,主要用于模型或系統(tǒng)間的解耦,提高系統(tǒng)的可擴(kuò)展性和可維護(hù)性。
1.1. 什么是領(lǐng)域事件
領(lǐng)域事件是領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design,簡(jiǎn)稱(chēng)DDD)中的一個(gè)重要概念,特指在領(lǐng)域模型中發(fā)生的有意義的事件,是對(duì)領(lǐng)域模型中的重要業(yè)務(wù)動(dòng)作執(zhí)行結(jié)果的抽象,如訂單創(chuàng)建、支付完成等。
在DDD中,領(lǐng)域事件是一種用于傳遞信息的機(jī)制,它使得不同領(lǐng)域模型之間的通信變得更加簡(jiǎn)單和靈活。通過(guò)將事件分發(fā)給相關(guān)的訂閱者,可以讓不同的領(lǐng)域模型之間實(shí)現(xiàn)松耦合,從而更容易擴(kuò)展和維護(hù)應(yīng)用程序。
領(lǐng)域事件通常由領(lǐng)域?qū)ο笾鲃?dòng)觸發(fā)并發(fā)布,而事件處理器則負(fù)責(zé)訂閱事件并對(duì)事件進(jìn)行處理。通過(guò)事件發(fā)布和訂閱機(jī)制,可以在應(yīng)用程序中實(shí)現(xiàn)高效的事件驅(qū)動(dòng)架構(gòu),從而更好地支持復(fù)雜的業(yè)務(wù)邏輯和業(yè)務(wù)流程。
說(shuō)起來(lái)有點(diǎn)抽象,簡(jiǎn)單舉個(gè)例子:假設(shè)有一個(gè)電子商務(wù)系統(tǒng),用戶(hù)下單后需要生成訂單并發(fā)送通知給相關(guān)人員。在領(lǐng)域模型中,可以定義一個(gè) Order 領(lǐng)域?qū)ο?,該?duì)象可以包含多個(gè)屬性,如訂單號(hào)、下單時(shí)間、購(gòu)買(mǎi)的商品信息、收貨地址等等。當(dāng)用戶(hù)下單時(shí),可以通過(guò)調(diào)用 Order 對(duì)象的方法來(lái)生成訂單,同時(shí)也可以通過(guò)領(lǐng)域事件來(lái)發(fā)送通知。
具體來(lái)說(shuō),可以定義一個(gè) OrderCreated 領(lǐng)域事件,用于表示訂單創(chuàng)建完成的事件,該事件包含一些必要的屬性,如訂單號(hào)、下單時(shí)間、購(gòu)買(mǎi)的商品信息、收貨地址等等。當(dāng) Order 對(duì)象創(chuàng)建完成后,可以通過(guò)領(lǐng)域事件來(lái)觸發(fā)發(fā)送通知的操作,比如發(fā)送郵件或短信通知相關(guān)人員。
1.2. 領(lǐng)域事件的應(yīng)用場(chǎng)景
領(lǐng)域事件的應(yīng)用創(chuàng)建眾多,從圖中可以看出:
領(lǐng)域事件可以:
保證聚合間的數(shù)據(jù)一致性。當(dāng)一個(gè)聚合根上的操作引發(fā)了其他聚合根的變更時(shí),將這些變更作為領(lǐng)域事件發(fā)布出去,其他聚合根可以訂閱這些事件并更新自己的狀態(tài),從而實(shí)現(xiàn)最終一致性。
替換批量處理。可以作為任務(wù)的觸發(fā)器,例如定時(shí)任務(wù)、異步任務(wù),避免定時(shí)+掃描這類(lèi)批量處理。
實(shí)現(xiàn)事件源模式。將所有的領(lǐng)域事件全部存儲(chǔ)下來(lái),可以用于恢復(fù)聚合的狀態(tài),實(shí)現(xiàn)事件源模式;也可以用于后續(xù)的審計(jì)和調(diào)試。
進(jìn)行限界上下文集成。將事件從一個(gè)子域發(fā)布到另一個(gè)子域,使得這兩個(gè)子域可以解耦,不用相互知道彼此的存在。
領(lǐng)域事件雖好,但仍需技術(shù)框架進(jìn)行支持,其實(shí) Spring 的 Event 機(jī)制就足以滿(mǎn)足各類(lèi)需求。
2. Spring 對(duì) Event 的支持
在 Spring 中,事件的處理可以通過(guò)三種方式來(lái)實(shí)現(xiàn):
基于接口的事件處理:通過(guò)實(shí)現(xiàn) ApplicationListener 接口并重寫(xiě) onApplicationEvent 方法來(lái)處理事件。
基于注解的事件處理:通過(guò)在方法上添加 @EventListener 或 @TransactionEventListener 注解來(lái)處理事件,可以指定事件的類(lèi)型以及監(jiān)聽(tīng)的條件等。
基于異步事件處理:通過(guò)使用 @Async 注解來(lái)異步處理事件,可以提高應(yīng)用程序的響應(yīng)速度。
2.1. 基于接口的事件處理
由于與 Spring 存在強(qiáng)耦合,現(xiàn)在已經(jīng)很少使用,可以直接跳過(guò)。
下面是一個(gè)基于接口的事件處理的示例代碼:
@Component publicclassMyEventListenerimplementsApplicationListener{ @Override publicvoidonApplicationEvent(MyEventevent){ //處理事件 System.out.println("Receivedevent:"+event.getMessage()); } } publicclassMyEvent{ privateStringmessage; publicMyEvent(Stringmessage){ this.message=message; } publicStringgetMessage(){ returnmessage; } } @Component publicclassMyEventPublisher{ @Autowired privateApplicationEventPublishereventPublisher; publicvoidpublishEvent(Stringmessage){ MyEventevent=newMyEvent(message); eventPublisher.publishEvent(event); } }
在這個(gè)示例中,MyEvent 是一個(gè)自定義的事件類(lèi),MyEventListener 是一個(gè)實(shí)現(xiàn)了 ApplicationListener 接口的監(jiān)聽(tīng)器,用于處理 MyEvent 事件,MyEventPublisher 是用于發(fā)布事件的類(lèi)。
當(dāng)應(yīng)用程序調(diào)用 MyEventPublisher 的 publishEvent 方法時(shí),會(huì)觸發(fā)一個(gè) MyEvent 事件,MyEventListener 中的 onApplicationEvent 方法將被自動(dòng)調(diào)用,從而處理這個(gè)事件。
2.2. 基于注解的事件處理
Spring 提供 @EventListener 和 @TransactionListener 兩個(gè)注解以簡(jiǎn)化對(duì)事件的處理。
2.2.1. @EventListener
Spring 的 EventListener 監(jiān)聽(tīng)器是一種相對(duì)于傳統(tǒng)的事件監(jiān)聽(tīng)方式更為簡(jiǎn)潔和靈活的事件機(jī)制。與傳統(tǒng)的事件機(jī)制不同,EventListener 不需要顯示地繼承特定的事件接口,而是使用注解標(biāo)識(shí)需要監(jiān)聽(tīng)的事件類(lèi)型,然后通過(guò)一個(gè)單獨(dú)的監(jiān)聽(tīng)器類(lèi)處理所有類(lèi)型的事件。
相比之下 EventListener 的優(yōu)勢(shì)主要有以下幾點(diǎn):
更加靈活:EventListener 不依賴(lài)于任何特定的事件接口,從而使得事件處理更加靈活,可以監(jiān)聽(tīng)和處理任意類(lèi)型的事件。
更加簡(jiǎn)潔:相比傳統(tǒng)的事件監(jiān)聽(tīng)方式,使用 EventListener 可以避免一系列繁瑣的接口定義和實(shí)現(xiàn),簡(jiǎn)化了代碼結(jié)構(gòu),使得開(kāi)發(fā)效率更高。
更加松耦合:EventListener 將事件發(fā)布方和事件處理方分離,遵循松耦合的設(shè)計(jì)原則,提高了代碼的可維護(hù)性和擴(kuò)展性。
更加可測(cè)試:由于 EventListener 可以監(jiān)聽(tīng)和處理任意類(lèi)型的事件,可以通過(guò)單元測(cè)試驗(yàn)證其功能是否正確,從而提高了測(cè)試的可靠性。
以下是一個(gè)簡(jiǎn)單的例子:
@Component publicclassMyEventListener{ @EventListener publicvoidonApplicationEvent(MyEventevent){ //處理事件 System.out.println("Receivedevent:"+event.getMessage()); } } publicclassMyEvent{ privateStringmessage; publicMyEvent(Stringmessage){ this.message=message; } publicStringgetMessage(){ returnmessage; } } @Component publicclassMyEventPublisher{ @Autowired privateApplicationEventPublishereventPublisher; publicvoidpublishEvent(Stringmessage){ MyEventevent=newMyEvent(message); eventPublisher.publishEvent(event); } }
相比基于接口的事件處理,EventListener 是一種更加簡(jiǎn)潔、靈活、松耦合、可測(cè)試的事件機(jī)制,能夠有效地降低開(kāi)發(fā)的復(fù)雜度,提高開(kāi)發(fā)效率。
2.2.2. @TransactionEventListener
在 Spring 中,TransactionEventListner 和 EventListner 都是用于處理事件的接口。不同之處在于
TransactionEventListner 是在事務(wù)提交后才會(huì)觸發(fā)
而 EventListner 則是在事件發(fā)布后就會(huì)觸發(fā)。
具體來(lái)說(shuō),在使用 Spring 的聲明式事務(wù)時(shí),可以在事務(wù)提交后觸發(fā)某些事件。這就是 TransactionEventListner 的應(yīng)用場(chǎng)景。而 EventListner 則不涉及事務(wù),可以用于在事件發(fā)布后觸發(fā)一些操作。
下面是一個(gè)簡(jiǎn)單的示例,演示了如何使用 TransactionEventListner 和 EventListner:
@Component publicclassMyEventListener{ @EventListener publicvoidhandleMyEvent(MyEventevent){ //處理MyEvent } @TransactionalEventListener publicvoidhandleMyTransactionalEvent(MyTransactionalEventevent){ //處理MyTransactionalEvent } } @Service publicclassMyService{ @Autowired privateApplicationEventPublishereventPublisher; @Autowired privateMyRepositorymyRepository; @Transactional publicvoiddoSomething(){ //做一些事情 MyEntityentity=myRepository.findById(1L); //發(fā)布事件 eventPublisher.publishEvent(newMyEvent(this,entity)); //發(fā)布事務(wù)事件 eventPublisher.publishEvent(newMyTransactionalEvent(this,entity)); } }
在這個(gè)例子中,MyEventListener 類(lèi)定義了兩個(gè)方法,handleMyEvent 和 handleMyTransactionalEvent,分別處理 MyEvent 和 MyTransactionalEvent 事件。其中,handleMyTransactionalEvent 方法用 @TransactionalEventListener 注解標(biāo)記,表示它只會(huì)在事務(wù)提交后觸發(fā)。
MyService 類(lèi)中的 doSomething 方法使用 ApplicationEventPublisher 來(lái)發(fā)布事件。注意,它發(fā)布了兩種不同類(lèi)型的事件:MyEvent 和 MyTransactionalEvent。這兩個(gè)事件會(huì)分別觸發(fā) MyEventListener 中的對(duì)應(yīng)方法。
總的來(lái)說(shuō),Spring 的事件機(jī)制非常靈活,可以方便地?cái)U(kuò)展應(yīng)用程序的功能。TransactionEventListner 和 EventListner 這兩個(gè)接口的應(yīng)用場(chǎng)景有所不同,可以根據(jù)實(shí)際需求選擇使用。
2.3.基于異步事件處理
@Async是Spring框架中的一個(gè)注解,用于將一個(gè)方法標(biāo)記為異步執(zhí)行。使用該注解,Spring將自動(dòng)為該方法創(chuàng)建一個(gè)新線程,使其在后臺(tái)異步執(zhí)行,不會(huì)阻塞主線程的執(zhí)行。
在具體應(yīng)用中,使用@Async可以大大提升應(yīng)用的并發(fā)處理能力,使得系統(tǒng)能夠更快地響應(yīng)用戶(hù)請(qǐng)求,提高系統(tǒng)的吞吐量。
@Async 和 @EventListener 或 @TransactionEventListener 注解在一起使用時(shí),會(huì)產(chǎn)生異步的事件處理器。使用這種組合的方式,事件處理器會(huì)在單獨(dú)的線程池中執(zhí)行,以避免阻塞主線程。這種方式在需要處理大量事件或者事件處理器耗時(shí)較長(zhǎng)的情況下非常有用,可以有效提高應(yīng)用的性能和可伸縮性。同時(shí),Spring 框架對(duì)這種方式也提供了完善的支持,可以方便地使用這種方式來(lái)實(shí)現(xiàn)異步事件處理。
下面是一個(gè)簡(jiǎn)單的示例代碼,演示了如何在 Spring 中使用 @Async 和 @EventListener 一起實(shí)現(xiàn)異步事件處理:
@Component publicclassExampleEventListener{ @Async @EventListener publicvoidhandleExampleEvent(ExampleEventevent){ //在新的線程中執(zhí)行異步邏輯 //... } }
在這個(gè)示例中,ExampleEventListener 類(lèi)中的 handleExampleEvent 方法使用了 @Async 和 @EventListener 注解,表示這個(gè)方法是一個(gè)異步事件監(jiān)聽(tīng)器。當(dāng)一個(gè) ExampleEvent 事件被觸發(fā)時(shí),這個(gè)方法會(huì)被異步地執(zhí)行。在這個(gè)方法中,可以執(zhí)行任何異步的邏輯處理,比如向隊(duì)列發(fā)送消息、調(diào)用其他服務(wù)等。
備注:在使用 @Async 時(shí),需要根據(jù)業(yè)務(wù)場(chǎng)景對(duì)線程池進(jìn)行自定義,以免出現(xiàn)資源不夠的情況(Spring 默認(rèn)使用單線程處理@Async異步任務(wù))
4. 場(chǎng)景分析
綜上所述,當(dāng)領(lǐng)域事件發(fā)出來(lái)之后,不同的注解會(huì)產(chǎn)生不同的行為,簡(jiǎn)單匯總?cè)缦拢?/p>
@EventListener | @TransactionEventListener | |
---|---|---|
無(wú) @Async | 順序、同步執(zhí)行 | 事務(wù)提交后、同步執(zhí)行 |
有 @Async | 順序、異步執(zhí)行 | 事務(wù)提交后、異步執(zhí)行 |
4.1. @EventListener
特點(diǎn):
順序執(zhí)行。調(diào)用 publish(Event) 后,自動(dòng)觸發(fā)對(duì) @EventListner 注釋方法的調(diào)用
同步執(zhí)行。使用主線程執(zhí)行,方法拋出異常會(huì)中斷調(diào)用鏈路,會(huì)觸發(fā)事務(wù)的回歸
應(yīng)用場(chǎng)景:
事務(wù)消息表。在同一事務(wù)中完成對(duì)業(yè)務(wù)數(shù)據(jù)和消息表的修改
業(yè)務(wù)驗(yàn)證。對(duì)業(yè)務(wù)對(duì)象進(jìn)行最后一次驗(yàn)證,如果驗(yàn)證不通過(guò)直接拋出異常中斷數(shù)據(jù)庫(kù)事務(wù)
業(yè)務(wù)插件。在當(dāng)前線程和事務(wù)中執(zhí)行插件完成業(yè)務(wù)擴(kuò)展
4.2. @TransactionEventListener
特點(diǎn):
事務(wù)提交后執(zhí)行。調(diào)用 publish(Event) 時(shí),只是向上下文中注冊(cè)了一個(gè)回調(diào)器,并不會(huì)立即執(zhí)行;只有在事務(wù)提交后,才會(huì)觸發(fā)對(duì) @TransactionEventListner 注釋方法的調(diào)用
同步執(zhí)行。使用主線程執(zhí)行,方法拋出異常會(huì)中斷調(diào)用鏈路,當(dāng)不會(huì)回歸事務(wù)(事務(wù)已提交,沒(méi)有辦法進(jìn)行回歸)
應(yīng)用場(chǎng)景:
數(shù)據(jù)同步。事務(wù)提交后,將變更同步到 ES 或 Cache
記錄審計(jì)日志。只有在業(yè)務(wù)變更成功更新到數(shù)據(jù)庫(kù)時(shí)才進(jìn)行記錄
備注:@TransactionEventLisnter 必須在事務(wù)上下文中,脫離上下文,調(diào)用不會(huì)生效
4.3. @EventListener + @Async
特點(diǎn):
順序執(zhí)行。調(diào)用 publish(Event) 后,自動(dòng)觸發(fā)對(duì) @EventListner 注釋方法的調(diào)用
異步執(zhí)行。使用獨(dú)立的線程池執(zhí)行任務(wù),方法拋出異常對(duì)主流程沒(méi)有任何影響
應(yīng)用場(chǎng)景:
記日志明細(xì)日志,輔助排查問(wèn)題
4.4. @TransactionEventListener + @Async
特點(diǎn):
事務(wù)提交后執(zhí)行。調(diào)用 publish(Event) 時(shí),只是向上下文中注冊(cè)了一個(gè)回調(diào)器,并不會(huì)立即執(zhí)行;只有在事務(wù)提交后,才會(huì)觸發(fā)對(duì) @TransactionEventListner 注釋方法的調(diào)用
異步執(zhí)行。使用獨(dú)立的線程池執(zhí)行任務(wù),方法拋出異常對(duì)主流程沒(méi)有任何影響
應(yīng)用場(chǎng)景:異步處理。記錄操作日志,異步保存數(shù)據(jù)等 備注:@TransactionEventLisnter 必須在事務(wù)上下文中,脫離上下文,調(diào)用不會(huì)生效
5. 小結(jié)
領(lǐng)域事件的落地,不僅需要強(qiáng)大的設(shè)計(jì)能力,還需要與之匹配的基礎(chǔ)設(shè)施。Spring 作為最常用的框架,基于發(fā)布訂閱實(shí)現(xiàn)了完整的一套 Event 管理機(jī)制。工具在手是否能根據(jù)業(yè)務(wù)場(chǎng)景選擇合適的解決方案就成了研發(fā)的職責(zé),簡(jiǎn)單思考以下組合適用場(chǎng)景是什么:
@EventListener
@TransactionEventListener
@EventListener + @Async
@TransactionEventListener + @Async
審核編輯:劉清
-
JAVA
+關(guān)注
關(guān)注
20文章
2983瀏覽量
106663 -
MySQL
+關(guān)注
關(guān)注
1文章
842瀏覽量
27424 -
MYSQL數(shù)據(jù)庫(kù)
+關(guān)注
關(guān)注
0文章
96瀏覽量
9726 -
ddd
+關(guān)注
關(guān)注
0文章
23瀏覽量
3009
原文標(biāo)題:Spring Event + DDD = 王炸?。?/p>
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
modusToolbox在加載離線庫(kù)時(shí),總是在loading device_db時(shí)提示錯(cuò)誤的原因?
使用MotorControl Workbench生成代碼時(shí)出錯(cuò)的原因?
How to download and install AD10 when using proxy servers
MATLAB請(qǐng)教各位大神
ESP-IDF使用:請(qǐng)問(wèn)怎么樣能測(cè)試esp32的spp example
STM32CubeMX芯片包(固件庫(kù))使用注意事項(xiàng)
STM32CubeIDE不顯示“Inizialize all peripherals with their Default Mode”怎么解決?
STM32CubeMX系列6版本刪除主文件夾中存在的所有文件夾,從而刪除用戶(hù)創(chuàng)建的文件夾要如何避免?
如何在Touchgfx設(shè)計(jì)器中開(kāi)始使用來(lái)自touchgfx-open-repository的圖形小部件?
如何在代碼中從touchgfx-open-repository添加二維碼小部件?
mirror和repository的區(qū)別分析

怎么樣才是一臺(tái)好電腦
pip安裝更換鏡像
Git的常規(guī)使用:Idea集成GitHub

評(píng)論