【DDD】學習筆記-發布者—訂閱者模式

在領域設計模型中引入了領域事件,并不意味著就采用了領域事件建模范式,此時的領域事件僅僅作為一種架構或設計模式而已,屬于領域設計模型的設計要素。在領域設計建模階段,如何選擇和設計領域事件,存在不同的模式,主要為發布者—訂閱者模式和事件溯源模式,它們可以統稱為“領域事件模式”。

發布者—訂閱者模式

發布者—訂閱者(Publisher-Subscriber)模式嚴格說來是一種架構模式,在領域驅動設計中,它通常用于限界上下文(或微服務)之間的通信與協作。為表區分,在領域模型內部使用事件進行狀態通知的模式屬于觀察者模式,不屬于發布者—訂閱者的范疇。

由于事件消息無需返回值,就使得事件的發布可以采用異步非阻塞模式,因此,采用事件的發布者—訂閱者模式不僅能夠解除限界上下文之間的耦合,還能提高系統的響應能力。如今,基于流的響應式編程也越來越成熟,如 Kafka 這樣的消息中間件通常又具有極強的吞吐能力和水平伸縮的集群能力,使得消息能夠以接近實時的性能得到處理。

當我們采用發布/訂閱事件來處理限界上下文之間的通信時,要明確限界上下文的邊界,進而決定事件消息傳遞的方式。如果相互通信的限界上下文處于同一個進程內,就要考慮:引入一個分布式的消息中間件究竟值不值得?分布式通信可能會帶來事務一致性、網絡可靠性等多方面的問題,與其如此,不如放棄選擇發布者—訂閱者模式,改為觀察者模式,又或者放棄分布式的消息中間件,選擇共享內存的事件總線,如采用本地 Actor 模式,由 Actor 對象內置的 MailBox 作為傳輸事件的本地總線,達到異步通信(非跨進程)的目的。

應用事件

如果選擇分布式的消息中間件實現發布者—訂閱者模式,則限界上下文之間傳遞的領域事件屬于外部事件。與之相對的是內部事件,它包含在限界上下文內的領域模型中。既然外部事件用于限界上下文之間,就應該由應用層的應用服務來負責發布生成和發布事件。由于外部事件和內部事件的定義過于含糊,考慮到這些事件所處的層次和邊界,我將外部事件稱之為“應用事件”,內部事件則保留為“領域事件”的名稱,這樣恰好可以與分層架構的應用層、領域層相對應。

應用事件與領域事件的作用不同。應用事件通常用于限界上下文之間的協作,由應用服務來負責,如果限界上下文的邊界為進程邊界,還需要考慮跨進程的事件消息通信。應用事件采用的模式為發布者—訂閱者模式。領域事件屬于領域模型的一部分,如果用于限界上下文內部之間的協作,采用的模式為觀察者模式;如果領域事件表達的是狀態遷移,采用的模式為事件溯源模式。發布一個領域事件就和創建一個領域對象一樣,都是內存中的操作。只是在持久化時,才需要訪問外部的資源。

如果一個事件既需要當前限界上下文關心,又需要跨限界上下文關心,那么,該事件就相同于同時扮演了領域事件和應用事件的角色。由于應用層依賴于領域層,即使是定義在領域層內部的領域事件,應用層也可以重用它。如果希望隔離外部限界上下文對領域事件的依賴,也可以將該領域事件轉換為應用事件。

應用事件作為協調限界上下文之間的協作消息,存在兩種不同的定義風格,Martin Fowler 將其分別命名為:事件通知(Event Notification)和事件攜帶狀態遷移(Event-Carried State Transfer)。注意,這兩種風格在發布者—訂閱者模式中,起到都是“觸發器”的作用。但兩種風格的設計思維卻如針尖對麥芒,前者降低了耦合,卻犧牲了限界上下文的自治性;后者恰好相反,在換來限界上下文的自治性的同時,卻是以模型耦合為代價的。

說明:Martin Fowler 在其文章?What do you mean by “Event-Driven”??中探討了所謂“事件驅動”的模式,除了上述的兩種模式之外,還有事件溯源與 CQRS 模式。但我認為前兩種模式屬于事件消息定義風格,主要用于發布者—訂閱者模式。發布者—訂閱者模式與 CQRS 模式同屬于架構模式,而事件溯源則屬于領域模型的設計模式。

由于應用事件要跨越限界上下文,倘若事件攜帶了當前限界上下文的領域模型對象,在分布式架構中,訂閱方就需要定義同等的包含了領域模型對象的應用事件。一旦應用事件攜帶的領域模型發生了變化,發布者與訂閱者雙方都要受到影響。為了避免這一問題,應用事件除了包含消息通知所必須具備的屬性之外,不要傳遞整個領域模型對象,僅需攜帶該領域模型對象的身份標識(ID)。這就是所謂的“事件通知”風格。

由于“事件通知”風格傳遞的應用事件是不完整的,倘若訂閱方需要進一步知道該領域模型對象的更多屬性,就需要通過 ID 調用發布方公開的遠程服務去獲取。服務的調用又為限界上下文引入了復雜的協作關系,反過來破壞了事件帶來的松散耦合。倘若將應用事件定義為一個相對自給自足的對象,就可以規避這些不必要的服務協作,提高了限界上下文的獨立性。這就是“事件攜帶狀態遷移”風格。

“事件攜帶狀態遷移”風格要求應用事件攜帶狀態,就可能需要在事件內部內嵌領域模型,導致發布方與訂閱方都需要重復定義領域模型。為避免重復,可以考慮引入共享內核來抽取公共的應用事件類,然后由發布者與訂閱者所在的限界上下文共享。若希望降低領域模型帶來的影響,也可以盡量保持應用事件的扁平結構,即將領域模型的屬性數據定義為語言框架的內建類型。如此一來,發布者與訂閱者雙方只需共享同一個應用事件結構即可,當然壞處是需要引入從領域模型到應用事件的轉換。

一個定義良好的應用事件應具備如下特征:

  • 事件屬性應以內建類型為主,保證事件的平臺中立性,減少甚至消除對領域模型的依賴
  • 發布者的聚合ID作為構成應用事件的主要內容
  • 保證應用事件屬性的最小集
  • 為應用事件定義版本號,支持對應用事件的版本管理
  • 為應用事件定義唯一的身份標識
  • 為應用事件定義創建時間戳,支持對事件的按序處理
  • 應用事件應是不變的對象

我們可以為應用事件定義一個抽象父類:

public class ApplicationEvent implements Serializable {protected final String eventId;protected final String createdTimestamp;protected final String version;public ApplicationEvent() {this("v1.0");}public ApplicationEvent(String version) {eventId = UUID.randomUUID().toString();createdTimestamp = new Timestamp(new Date().getTime()).toString();this.version = version;}  
}

在業務流程中,我們經常面對存在兩種操作結果的應用事件。不同的結果會導致不同的執行分支,響應事件的方式也有所不同。定義這樣的應用事件也存在兩種不同的形式。一種形式是將操作結果作為應用事件攜帶的值,例如支付完成事件:

public enum OperationResult {SUCCESS = 0, FAILURE = 1
}public class PaymentCompleted extends ApplicationEvent {private final String orderId;private final OperationResult paymentResult;public PaymentCompleted(String orderId, OperationResult  paymentResult) {super();this.orderId = orderId;this.paymentResult = paymentResult;}
}

采用這一定義的好處在于可以減少事件的個數。由于事件自身沒有體現具體的語義,事件訂閱者就需要根據 OperationResult 的值做分支判斷。若要保證訂閱者代碼的簡潔性,可以采用第二種形式,即通過事件類型直接表現操作的結果:

public class PaymentSucceeded extends ApplicationEvent {private final String orderId;public PaymentSucceeded (String orderId) {super();this.orderId = orderId;}
}public class PaymentFailed extends ApplicationEvent {private final String orderId;public PaymentFailed (String orderId) {super();this.orderId = orderId;}
}

這兩個事件定義的屬性完全相同,區別僅在于應用事件的類型。

微服務的協同模式

若將限界上下文視為微服務,則發布者—訂閱者模式遵循了協同(Choreography)模式來處理彼此之間的協作,這就決定了參與協作的各個限界上下文地位相同,并無主次之分。由于事件消息屬于異步通信模式,因此在運用發布者—訂閱者模式時,需要結合業務場景,明確哪些操作需要引入應用事件,由誰發布和訂閱應用事件。發布者—訂閱者模式并非排他性的模式,例如在執行查詢操作時,又或者執行的命令操作并不要求高響應能力時,亦可采用同步的開放主機服務模式。

若要追求微服務架構的一致性,保證微服務自身的自治性,可考慮在架構層面采用純粹的事件驅動架構(Event-Driven Architecture,EDA)。遵循事件驅動架構,微服務之間的協作皆采用異步的事件通信模式。即使協作方式為查詢操作,也可使用事件流在服務本地緩存數據集,從而保證在執行查詢操作時僅需要執行本地查詢即可。要支持本地查詢,需要在每次發布事件時,對應的訂閱者負責獲取自己感興趣的數據,并將其緩存到本地服務的存儲庫中。例如,下訂單場景需要訂單服務調用庫存查詢服務以驗證商品是否滿足庫存條件。若要避免跨服務之間的同步查詢操作,就需要訂單服務事先訂閱庫存事件流,并將該庫存事件流保存在訂單服務的本地數據庫中。庫存服務的每次變更都會發布事件,訂單服務會訂閱該事件,然后將其同步到庫存事件流,以保證訂單服務緩存的庫存事件流是最新的。

既然限界上下文的協作方式發生了變化,意味著應用服務之間的調用方式也將隨之改變。

在買家下訂單的業務場景中,考慮訂單上下文與支付上下文之間的協作關系。如果采用開放主機模式,則訂單上下文將作為下游發起對支付服務的調用。支付成功后,訂單狀態被修改為“已支付”,按照流程就需要發送郵件通知買家訂單已創建成功,同時通知賣家發貨。這時,訂單上下文會作為下游發起對通知服務的調用。顯然,在這個業務場景中,訂單上下文成為了整個協作過程的“樞紐站”:

70835542.png

發布者—訂閱者模式就完全不同了。限界上下文成為了真正意義上的自治單元,它根本不用理會其他限界上下文。它像一頭敏捷的獵豹一般游走在自己的領土疆域內,凝神靜聽,伺機而動,一旦自己關心的事件發布,就迅猛地將事件“叼”走,然后利用自己的業務邏輯去“消化”它,并在滿足業務條件的時候,發布自己的事件“感言”,至于會是誰對自己發布的事件感興趣,就不在它的考慮范圍內了。顯然,采用事件風格設計的限界上下文都是各掃門前雪,彼此具有平等的地位:

79256322.png

訂單上下文既訂閱了支付上下文發布的 PaymentCompleted 事件,又會在更新訂單狀態之后,發布 OrderPaid 事件。假定我們選擇 Kafka 作為消息中間件,就可以在訂單上下文定義一個事件訂閱者,偵聽指定主題的事件消息。該事件訂閱器是當前限界上下文的北向網關:

public class PaymentEventSubscriber {private ApplicationEventHandler eventHandler;@KafkaListener(id = "payment", clientIdPrefix = "payment", topics = {"topic.ecommerce.payment"}, containerFactory = "containerFactory")public void subscribeEvent(String eventData) { ApplicationEvent event = json.deserialize<PaymentCompleted>(eventData);eventHandler.handle(event);}
}

ApplicationEventHandler 是一個接口,凡是需要處理事件的應用服務都可以實現它。例如 OrderAppService:

public class OrderAppService implements ApplicationEventHandler {private UpdatingOrderStatusService updatingService;private ApplicationEventPublisher eventPublisher;public void handle(ApplicationEvent event) {if (event instanceOf PaymentCompleted) {onPaymentCompleted((PaymentCompleted)event);} else {...}}private void onPaymentCompleted(PaymentCompleted paymentEvent) {if (paymentEvent.OperationResult == OperationResult.SUCCESS) {updatingSerivce.execute(OrderStatus.PAID);          ApplicationEvent orderPaid = composeOrderPaidEvent(paymentEvent.orderId());      eventPublisher.publishEvent(“payment", orderPaid);} else {...}}
}

OrderAppService 應用服務通過 ApplicationEventPublisher 發布事件。這是一個抽象接口,扮演了南向網關的作用,它的實現屬于基礎設施層,依賴了 Kafka 提供的 kafka-client 框架,通過調用該框架定義的 KafkaTemplate 發布應用事件:

public class ApplicationEventKafkaProducer implements ApplicaitonEventPublisher {private KafkaTemplate<String, String> kafkaTemplate;public void publishEvent(String topic, ApplicationEvent event) {kafkaTemplate.send(topic, json.serialize(event);}
}

采用發布者—訂閱者模式實現限界上下文之間的協作時,要注意應用層對領域邏輯的保護與控制,確保領域邏輯的純粹性。領域層的領域模型對象并未包含應用事件。應用事件屬于應用層,類似服務調用的數據契約對象。事件的訂閱與發布屬于基礎設施層:前者屬于北向網關,可以直接依賴消息中間件提供的基礎設施;后者屬于南向網關,應用服務需要調用它,為滿足整潔架構要求,需要對其進行抽象,再通過依賴注入到應用服務。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/697788.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/697788.shtml
英文地址,請注明出處:http://en.pswp.cn/news/697788.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

nginx-ingress-controller組件中Nginx的版本升級

參考鏈接&#xff1a;https://blog.csdn.net/qq_22824481/article/details/133761302 https://blog.csdn.net/mengfanshaoxia/article/details/127155020 https://blog.csdn.net/weixin_39961559/article/details/87935873 概要 業務區k…

JAVAEE初階 JVM(一)

JVM的熱門話題 一. JVM中的內存區域劃分1.經典筆試題. 二. JVM的類加載機制 一. JVM中的內存區域劃分 1.經典筆試題. 二. JVM的類加載機制

wondows10用Electron打包threejs的項目記錄

背景 電腦是用的mac&#xff0c;安裝了parallels desktop ,想用electron 想同時打包出 蘋果版本和windows版本。因為是在虛擬機里安裝&#xff0c;它常被我重裝&#xff0c;所以記錄一下打包的整個過程。另外就是node生態太活躍&#xff0c;幾個依賴沒記錄具體版本&#xff0…

lora網關智慧工廠三色燈安燈狀態采集鋇錸技術S281

LoRa網關結合鋇錸技術S281模塊在智慧工廠三色燈安燈狀態采集方面具有廣泛的應用前景。智慧工廠的安全生產管理對于企業生產經營至關重要&#xff0c;而三色燈安燈是工廠安全生產管理的重要指示燈&#xff0c;通過LoRa無線通信技術和鋇錸技術S281模塊&#xff0c;可以實現對三色…

android 使用X264編碼視頻

android 使用X264編碼視頻 源碼剛上傳可能審核 源碼下載地址 X264對應部分API介紹 初始化x264_param_t _x264_param new x264_param_t;/*** preset是編碼速度* 可選項"ultrafast", "superfast", "veryfast", "faster", "fa…

使用 package.json 配置代理解決 React 項目中的跨域請求問題

使用 package.json 配置代理解決 React 項目中的跨域請求問題 當我們在開發前端應用時&#xff0c;經常會遇到跨域請求的問題。為了解決這個問題&#xff0c;我們可以通過配置代理來實現在開發環境中向后端服務器發送請求。 在 React 項目中&#xff0c;我們可以使用 package…

MES系統中的手動排產和自動排產-助力生產效率

企業在排產管理中面臨的問題&#xff1a; 大多數的企業在調度排產過程中&#xff0c;都會遇到以下問題。首先是插單非常的多&#xff0c;計劃調整困難&#xff0c;會經常性的發生原材料、零部件的備貨不足。計劃按MRP或庫存展示計算出需求后將產生大量工單&#xff0c;這些工單…

《劍指Offer》筆記題解思路技巧優化_Part_6

《劍指Offer》筆記&題解&思路&技巧&優化_Part_6 &#x1f60d;&#x1f60d;&#x1f60d; 相知&#x1f64c;&#x1f64c;&#x1f64c; 相識&#x1f622;&#x1f622;&#x1f622; 開始刷題&#x1f7e1;1.LCR 168. 丑數—— 丑數&#x1f7e2;2. LCR 16…

Kubernetes服務網絡Ingress網絡模型分析、安裝和高級用法

文章目錄 1、Ingres簡介2、Ingres網絡模型分析3、安裝Ingress4、使用4.1、搭建測試環境4.2、域名訪問4.3、路徑重寫&#xff08;高級用法&#xff09;4.4、流量限制&#xff08;高級用法&#xff09; 5、總結 1、Ingres簡介 Ingress翻譯過來是“入口”的意思&#xff0c;也就是…

切換分支時候IDEA提示:workspace associated with branch feature has been restored

切換分支時候IDEA提示&#xff1a;workspace associated with branch feature has been restored 這個消息是指與"feature"分支關聯的工作區已經恢復。在Git中&#xff0c;工作區是指你當前正在進行修改和編輯的文件和目錄。當你切換分支時&#xff0c;Git會自動將工…

配置docker 支持GPU方法(Nvidia GPU)

參考官方文檔&#xff1a; https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html 系統版本&#xff1a;ubuntu 23.04 執行腳本如下&#xff1a; 1.Configure the production repository: curl -fsSL https://nvidia.github.io/lib…

怎么把試卷圖片轉換成word?這4種方法一看就會

怎么把試卷圖片轉換成word&#xff1f;在數字化日益盛行的今天&#xff0c;我們常常會面臨將紙質試卷或圖片中的試卷內容轉化為Word文檔的需求。無論是為了對試卷內容進行編輯、修改&#xff0c;還是為了在線共享、遠程教學&#xff0c;將圖片轉換為Word文檔都成為了至關重要的…

集成TinyMCE富文本編輯器

若依的基礎上集成TinyMCE富文本編輯器 前端bootstrap TinyMCE官網鏈接 TinyMCE所需靜態資源下載鏈接 開源項目-若依鏈接 將TinyMCE靜態資源包放入項目中&#xff1b; 代碼引入css&#xff1a; <!-- 引入TinyMCE CSS --><link th:href"{/ajax/libs/tinymce/j…

金田金業: 美聯儲泡沫正在破裂! 崩潰對黃金非常有利

The Great Recession Blog作者大衛哈吉斯表示&#xff0c;美聯儲一直以來都將繼續收緊貨幣政策&#xff0c;直到出現問題&#xff0c;但市場現在已經陷入泡沫。 他指出&#xff0c;泡沫正在破裂&#xff0c;崩潰最終將對黃金非常有利。 正當投資者聚焦美聯儲何時會降息&#xf…

Springboot 使用升級小記-循環依賴

springboot 使用已經非常廣泛了&#xff0c;它的版本迭代速度也比較快&#xff0c;過一段時間版本差異就會比較大。 由于低版本依賴的 spring 版本有漏洞問題&#xff0c;這次我們是從 2.2.6 版本直接升級到 2.7.16&#xff0c;升級 3.0 的話擔心差異更大。 這時直接修改依賴…

Jmeter 學習目錄(0)

Jmeter 所有內容均以學習為主輸出內容&#xff0c;按照最小單位和基礎進行輸出。 如果有看不懂&#xff0c;或者有不明確的內容&#xff0c;歡迎大家留言說明。 Mac Jmeter下載安裝啟動(1) Jmeter 目錄介紹(2) Jmeter基礎發起一次請求(3)

代碼隨想錄三刷day07

提示&#xff1a;文章寫完后&#xff0c;目錄可以自動生成&#xff0c;如何生成可參考右邊的幫助文檔 文章目錄 前言一、力扣206. 反轉鏈表二、力扣24. 兩兩交換鏈表中的節點 前言 遞歸寫法和雙指針法實質上都是從前往后翻轉指針指向&#xff0c;其實還有另外一種與雙指針法不同…

SD-WAN:快速改造升級企業原有網絡架構

隨著企業信息化的推進&#xff0c;傳統網絡架構已難以滿足企業日益復雜和多樣化的組網互聯需求。企業在不斷提高對網絡的要求&#xff0c;包括各辦公點的互聯數據傳輸、資源共享、視頻會議、ERP、OA、郵箱系統、云服務等應用需求&#xff0c;以及對網絡運維工作的簡化和降低難度…

Spring Event 快速入門

請直接看原文 : Spring Event&#xff0c;賊好用的業務解耦神器&#xff01; (qq.com) -------------------------------------------------------------------------------------------------------------------------------- 前言 Spring Event 同步使用 Spring Event 異…

架構篇35:微服務架構最佳實踐 - 方法篇

文章目錄 服務粒度拆分方法基礎設施小結上一篇我們談了實施微服務需要避免踩的陷阱,簡單提煉為: 微服務拆分過細,過分強調“small”。微服務基礎設施不健全,忽略了“automated”。微服務并不輕量級,規模大了后,“lightweight”不再適應。針對這些問題,我們看看微服務最佳…