領域事件和整合事件沒那麼高大上

Code綜藝圈發表於2022-04-19

前言

隨著系統架構的演變,有很多名詞也隨之湧現,如:微服務、灰度釋出、資源隔離、容器、領域/整合事件等,聽著的確高大上,讓很多小夥伴有一種無法征服的感覺;其實很多東西可能之前就已經用過了,只是名字不這麼叫而已,就算沒應用上也別慌,現在很多輪子都很成熟,用起來很容易上手的。這裡就來說說比較常見的領域事件和整合事件。

正文

1.概述

微服務和DDD盛行的時代,領域事件和整合事件經常被提及到;對於事件,小夥伴可以根據不同場景去理解,比如點選一個按鈕時,這個就是一個事件(點選事件),又或者說當購買商品時付款成功,也可以理解為一個事件,就像我們們在生活中對每一件事的定義是一樣的。

1.1 領域事件

領域事件(Domain Event)是在一個特定領域由一個動作觸發的,是發生在過去的行為產生的事件(行為可以是人操作的,也可以是系統自動的)

其實在專案中,通常我們們會把領域事件用在一個應用程式程式內,比如說在使用者管理時,當使用者註冊成功時,需要傳送郵件或簡訊提醒; 其中使用者管理可以簡單理解為一個領域,使用者註冊成功就是事件,而傳送提醒就是針對事件的處理方式。

這裡可能對領域的比喻不是特別恰當,如果小夥伴想更多瞭解,可以看看DDD(領域驅動設計)相關資料。

對於我個人的理解,我認為領域事件的主要目的是為了讓程式碼更加容易維護,讓業務更加容易擴充套件,也就是對程式碼業務層面的優化。如下圖:

對於原始這種方式,相信很多小夥伴也和我曾經一樣寫過這樣的程式碼邏輯,不用想什麼程式碼順序,直接擼碼就行了,但是這樣擴充套件性不好,比如我想加一個微信傳送怎麼辦,還得在原來基礎上繼續加,如果過兩天不要簡訊傳送了,還得去改原來程式碼。這樣是不是違背了軟體開發的開閉原則,儘量還是少改原有邏輯的程式碼,避免重複修改、重複測試。

對於優化後的這種方式,只需要在註冊成功之後釋出一個事件出來就行了,至於後面要傳送什麼樣的訊息不用管,只要捕獲到事件訊息,只需新增額外擴充套件的處理器類即可,就算是取消,只需不捕獲對應的事件就行,無需改動原有使用者註冊程式碼的邏輯。這種方式的事件就可以理解為領域事件。

小夥伴回想一下,之前在優化程式碼的時候是不是也這樣做過,只是當時不稱它為領域事件。

1.2 整合事件

整合事件(IntegrationEvent)同樣也是指在過去的行為產生的事件(行為可以是人操作的,也可以是系統自動的),一般用於跨多個微服務或外部系統。

比如現在的電商系統,訂單模組和物流模組會拆分為不同微服務,通常在訂單支付成功之後,物流模組需要知道訂單相關的明細,這樣才會根據訂單進行物流跟蹤。所以訂單在付款成功之後,就會釋出一個事件出去,物流系統訂閱到事件之後就可以處理對應業務邏輯。

對於整合事件的主要目的我認為就是為了讓服務模組之間或系統之間的對接耦合性變低,只要約定好事件型別,發事件模組和處理事件的模組就會有很少對接,便於擴充套件和維護。如圖:

原始的這種方式,像我有點年長的小夥伴應該之前都用過,當然現在有很多傳統企業專案也是這種方式。這種方式主要是通過介面的形式進行模組或系統之間的對接,這樣對接成本稍微偏高,因為訂單服務還需要開發呼叫物流服務介面的邏輯,還要各種聯調,考慮介面超時、失敗等各種情況;另外如果還有其他業務模組的系統需要對接怎麼辦,如果介面規範不一樣,還得重複再開發一套呼叫邏輯,這樣後面訂單服務這個模組就變得很臃腫,而且模組間的耦合性比較高。

優化後的方式就相對來說比較好,對訂單付款成功之後,只需將事件釋出出去就行了,剩下的不用過多幹涉,對應的業務模組訂閱到訊息之後進行相關業務處理即可;這種模式就算有其他業務模組加入也會很便捷,模組間的耦合性比較低。由於模組間的訊息需要傳輸,所以就需要EventBus來做這個事了。這種方式看上去不錯,但需要第三方的訊息中介軟體做訊息轉發和儲存,如RabbitMq、Kafka等;另外使用過程中,訊息的持久化、訊息丟失的情況都需要考慮,後續會單獨出相關係列的文章說這塊。

2.演示

對於技術落地,大神都把輪子造好了,我們們拿來就可以用啦。

2.1 領域事件
  • 技術簡介

    MediatR是用.Net實現的簡單中介者模式,無需其他依賴就能處理程式內的訊息傳遞,支援請求/響應、命令、查詢、通知和事件的同步或非同步傳遞,通過C#的泛型智慧排程

    這裡就不詳細說了,詳細說明小夥伴們可以看我之前分享的這篇文章《跟我一起學.NetCore之MediatR好像有點火

  • 案例實操

    準備一個API專案,引入對應的Nuget包,並註冊相關服務,如下:

    模擬使用者註冊成功釋出領域事件,這裡在預設的控制器裡新增一個介面進行測試,程式碼如下:

    釋出的事件資訊其實就是一個物件資訊,只是該類按照MediatR約定繼承對應的介面即可,如下:

    增加對事件的處理邏輯,即捕獲到事件之後如何處理,程式碼如下:

    這個處理類可以根據需要增加,這裡增加一個郵件的和簡訊的,如果還需要其他方式的,只需要按照約定繼承對應的介面,並實現對應的方法處理業務邏輯即可。

    執行起來看效果:

    是不是用起來很Easy~~~,整合事件也是一樣。

2.2 整合事件
  • 技術簡介

    這塊自己比較常用是CAP和Masstransit,關於CAP自己也分享過一篇文章《分散式事務最終一致性-CAP框架輕鬆搞定》,也可以用其進行事件的釋出,這裡就不在贅述。

    Masstransit是一個免費的、開源的.NET 分散式應用程式框架,輕量級的訊息匯流排(EventBus) ,即專門用來傳輸和接收訊息的;整合很多訊息中介軟體,如:RabbitMQ、AcitveMQ、Azure Service Bus、Kafka、Redis等,這裡我們主要說應用,更多詳情小夥伴們檢視官網,如下:

    官網地址:http://masstransit-project.com/getting-started/

    開源地址:https://github.com/MassTransit/MassTransit

  • 案例實操

    其實可以用記憶體的方式進行演示,但為了更符合真實場景,這裡採用RabbitMQ的方式進行演示,所以首先需要安裝RabbitMQ,為了方便,還是用Docker的方式進行安裝,如果對Docker還不瞭解的小夥伴,可以查閱我分享的系列文章《Docker系列》。

    在確保有Docker的環境下,執行如下命令即可:

    docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
    

    這個命令指定了預設使用者為admin,預設密碼也是admin,如果能正常登陸RabbitMQ系統,代表安裝成功了,如下:

    注:我這裡用的是阿里雲伺服器,所以需要在安全組和防火牆中開啟埠15672和5672的訪問,15672是RabbitMQ的Web介面,5672是程式之間通訊需要用到的。

    準備兩個API專案,一個模擬訂單釋出事件,一個模擬物流訂閱事件,首先都需要引入相關的Nuget包:MassTransit、MassTransit.RabbitMQ,並註冊相關服務,如下:

    模擬訂單釋出付款成功事件,在預設的控制器中增加對應的介面進行演示,如下:

    事件的定義這裡為了方便直接定義為公共的類庫,同樣就是一個簡單的類,裡面的內容如下:

    物流模組

    模擬物流訂閱付完款成功事件,這裡需要稍微注意一下,因為訂閱到事件之後需要進行相關的業務處理,所以在註冊服務的時候,需要把對應的處理器也註冊上,處理類的邏輯如下:

    將處理器也進行註冊,如下:

    啟動時候訂單的埠為5001,物流模組的埠為5000,只要避免兩個模組的埠不一樣就行,埠不能重複用,這樣就可以執行看效果了,兩個模組都啟動起來:

    在訂單模組訪問釋出介面,物流模組收到事件訊息之後就會馬上處理如下:

    注:以上演示方式沒有指定對應佇列,所以採用的是RabbitMQ中的Fanout模式,Fanout是一種廣播機制的釋出與訂閱模式,也就是所有的訂閱者都可以收到生產者釋出的事件,實際場景中這種模式用的不多,通常比較常用的是direct模式,小夥伴可以根據實際情況指定即可;關於RabbitMQ系列的文章我正在整理,後續會分享給大家。

相關原始碼地址:https://gitee.com/CodeZoe/dot-net-core-study-demo

總結

關於領域事件和整合事件的介紹和使用暫時先說這麼多,只是簡單介紹了我對領域事件和整合事件的理解及應用,更多細節還得小夥伴根據實際業務需求進配置和改進,但用法就是這麼簡單;對於訊息丟失、持久化等相關問題,後續會跟隨訊息佇列的文章分享出來。

關注“Code綜藝圈”,和我一起學習吧。

相關文章