MediatR 在.NET应用中的实践
MediatR 簡介
MediatR是.NET中的開源簡單中介者模式實現(xiàn).它通過一種進程內(nèi)消息傳遞機制(無其他外部依賴),進行請求/響應、命令、查詢、通知和事件的消息傳遞,并通過泛型來支持消息的智能調(diào)度。開源庫地址是https://github.com/jbogard/MediatR
MediatR的作者是Jimmy Bogard,他也是大名鼎鼎的AutoMapper的作者。如果你的英文還不錯,推薦你到https://jimmybogard.com上拜讀一下他博客文章,相信對你會有益處的。
中介者模式
既然MediatR是中介者模式的一種實現(xiàn),那么我們有必要簡單的了解一下什么是中介者模式。
?定義一個中介對象來封裝一系列對象之間的交互,使原有對象之間的耦合松散,且可以獨立地改變它們之間的交互。中介者模式又叫調(diào)停模式,它是迪米特法則的典型應用。
?普通模式下,常常會出現(xiàn)好多對象之間存在復雜的直接交互關系,這種交互關系常常是“網(wǎng)狀結構”,它要求每個對象都必須知道它需要交互的對象。在現(xiàn)實生活中,比如房產(chǎn)買賣如果沒有中介的情況,賣方要與無數(shù)的買方聯(lián)系,買方也與無數(shù)賣方聯(lián)系,通過極為復雜的網(wǎng)狀溝通才能獲得自己心儀的買(賣)家。如果把這種“網(wǎng)狀結構”改為“星形結構”的話,將大大降低它們之間的“耦合性”,這時只要找一個“中介者”就可以了。單一的買方或賣方都只跟一個中介服務人員聯(lián)系,中介會幫你甄別聯(lián)系另外一方。這樣將大大降低對象之間的耦合性。中介者模式是一種對象行為型模式,其主要優(yōu)點:
類之間各司其職,符合迪米特法則;
降低了對象之間的耦合性,使得對象易于獨立地被復用;
將對象間的一對多關聯(lián)轉(zhuǎn)變?yōu)橐粚σ坏年P聯(lián),提高靈活性,便于于維護和擴展。
MediatR 服務的注冊
添加Nuget包
在Visual Studio中添加下圖兩個包:也可以通過命令行工具添加:
dotnet?add?package?MediatR dotnet?add?package?MediatR.Extensions.Microsoft.DependencyInjection注冊MediatR
在.NET 6之前的ASP.NET Core項目中需要在Startup類中添加一下注冊:在.NET 6的應用中,則可在Program中添加注冊:
MediatR 的基本用法
MediatR有兩種消息傳遞的方式:
Request/Response,用于一個單獨的Handler。
Notification,用于多個Handler。
Request/Response
Request/Response?有點類似于 HTTP 的 Request/Response,發(fā)出一個 Request 會得到一個 Response。
Request?消息在 MediatR 中,有兩種類型:
IRequest<T>?返回一個T類型的值。
IRequest?不返回值。
對于每個 request 類型,都有相應的 handler 接口:
IRequestHandler<T, U>?實現(xiàn)該接口并返回?Task<U>
RequestHandler<T, U>?繼承該類并返回?U
IRequestHandler<T>?實現(xiàn)該接口并返回?Task<Unit>
AsyncRequestHandler<T>?繼承該類并返回?Task
RequestHandler<T>?繼承該類不返回
這樣一個創(chuàng)建訂單的命令和對應的處理程序,就如下圖所示:而在?Controller?中使用時,就簡單如下:
Notification
Notification?就是通知,調(diào)用者發(fā)出一次,然后可以有多個處理者參與處理。Notification?消息的定義很簡單,只需要讓你的類繼承一個空接口?INotification?即可。而處理程序則實現(xiàn)?INotificationHandler<T>?接口的?Handle?方法就行:有了上述定義后,只需要一行代碼即可完成調(diào)用:
await?_mediator.Publish(new?QueryOrder());然后,我們會得到如下結果:是不是很簡單呢?
注意:
「默認情況下」?通知的執(zhí)行過程不是異步的。Publish 方法調(diào)用后,MediatR 會將所有該通知的Handler依次執(zhí)行完好返回。也就是說如果一個通知的handler執(zhí)行需要1秒鐘,共有3個handler,則這個通知的Publish方法會執(zhí)行3秒鐘。作者在 Github 的 MediatR 庫中,給出了各種豐富場景的通知處理調(diào)度程序樣例代碼,開發(fā)者可以根據(jù)自己的業(yè)務情況自行定制修改 MediatR 的默認通知調(diào)度模式。
ISender 與 IPublisher
前面的例子中,我們都是直接使用的IMediator接口服務進行調(diào)用,MediatR 的作者在發(fā)布 9.0.0 版時,有意把原本孤立大一統(tǒng)的?IMediator?接口拆成了兩個?ISender?和?IPublisher,分別僅用于?Reuest/Response?和?Notification?場景,即:
ISender?接口只有?Send?方法
IPublisher?接口只有?Publish?方法
MediatR 的管線
.NET Core?一個大量存在但是被不少人忽視的概念就是?Pipeline,也就是管線。比如,ASP.NET Core?中的管線模型大概如下圖:這套管線模型可以使得我們在 HTTP Request 的真正處理邏輯之前,經(jīng)過一層層的管線邏輯對數(shù)據(jù)做預處理或者鑒權等;也可在處理邏輯返回結果后,在調(diào)用者得到響應前,由管線對結果進行二次加工。這就給我們帶來一個很好的分工協(xié)作模型,可以輕松應對「必然變化的客戶需求」,而不必修改核心業(yè)務邏輯代碼。畢竟,你知道「客戶的需求經(jīng)常還要改回去」!
MediatR?中具有與此類似的管線機制,可通過泛型接口?IPipelineBehavior<,>?來定義:使得我們在 Handler 的 Handle 真正執(zhí)行前或后可以額外做一些事情:記錄日志、對消息做校驗、對數(shù)據(jù)做預處理(如:把中文逗號改為英文逗號)、記錄性能較差的Handler 等等。
下面是我們對一個處理時長超過2秒的進行預警日志記錄的情景:
這時候可能有人會問了,我們怎么控制管線的執(zhí)行順序呢?嗯,這個問題很好,作者也早就想到了,MeidatR 的管線是通過注冊的順序來決定執(zhí)行的順序的。上圖中的性能記錄管線(RequestPerformanceBehavior)就會比數(shù)據(jù)驗證管線(RequestValidationBehavior)先執(zhí)行,畢竟驗證數(shù)據(jù)有時候也是需要花一些時間的。
「消息驗證管線是一個相對復雜的場景,我會在之后另起一篇單獨進行分享和說明?!?/strong>
注意
MediatR?中的管線有兩個比較特殊的預定:
IRequestPreProcessor<>?請求執(zhí)行前的預處理
IRequestPostProcessor<,>?請求執(zhí)行后的再處理
他們兩個的實現(xiàn)不必單獨注冊,在默認 MediatR 注冊邏輯中會自動注冊好,他們在所有管線中執(zhí)行的位置順序也就顯而易見了。
CQRS or DDD?
軟件開發(fā)發(fā)展到今天,模式和理念不斷在架構中刷新:從分布式到微服務,再到云原生 ……。時代對一個程序員,尤其是服務端程序員,提出的要求越來越高。DDD(領域驅(qū)動設計)在微服務架構中一再被提及,甚至有人提出這是必須項!
實施一個完美的 DDD 還是有難度的,現(xiàn)實中還有很多奮戰(zhàn)在一線的?CRUD?程序員還是不少。那么在 CRUD 和 DDD 之間我們是否還有緩沖區(qū)呢?MediatR 的作者曾經(jīng)也撰文討論過這個問題,我很認同他的基本觀點:設計是為應用服務的,不能為了 DDD 而 DDD。
CQRS?的全稱是:"Command and Query Responsibility Segregation",直譯過來就是命令與查詢責任分離,可以通俗的理解為?讀寫分離。
微軟的官方文檔中對此做過如下陳述:
?CQRS 命令和查詢責任分離數(shù)據(jù)存儲的讀取和更新操作分離的模式。在應用程序中實現(xiàn) CQRS 可以最大程度地提高其性能、可伸縮性和安全性。通過遷移到 CQRS 而創(chuàng)建的靈活性使系統(tǒng)能夠隨著時間的推移更好地發(fā)展,并防止更新命令在域級別導致合并沖突。
?微軟也給出了相應的隔離模型解決方案:
?CQRS 使用命令來更新數(shù)據(jù),使用查詢來讀取數(shù)據(jù),將讀取和寫入 分離到不同的 模型中。
命令應基于任務,而不是以數(shù)據(jù)為中心。
命令可以放置在隊列中進行異步處理,而不是同步處理。
查詢從不修改數(shù)據(jù)庫。查詢返回的 DTO 不封裝任何域知識。
CQRS 的好處包括:
?「獨立縮放」: CQRS 允許讀取和寫入工作負載獨立縮放,這可能會減少鎖爭用。
「優(yōu)化的數(shù)據(jù)架構」: 讀取端可使用針對查詢優(yōu)化的架構,寫入端可使用針對更新優(yōu)化的架構。
「安全性」: 更輕松地確保僅正確的域?qū)嶓w對數(shù)據(jù)執(zhí)行寫入操作。
「關注點分離」: 分離讀取和寫入端可使模型更易維護且更靈活。大多數(shù)復雜的業(yè)務邏輯被分到寫模型。讀模型會變得相對簡單。
「查詢更簡單」: 通過將具體化視圖存儲在讀取數(shù)據(jù)庫中,應用程序可在查詢時避免復雜聯(lián)接。
有了?MediatR?我們可以在應用中輕松實現(xiàn)?CQRS:
IRequest<>?的消息名稱以?Command?為結尾的是命令,其對應的 Handler 執(zhí)行「寫」任務
IRequest<>?的消息名稱以?Query?為結尾的是查詢,其對應的 Handler 執(zhí)行「讀」數(shù)據(jù)
結束語
MediatR?是一個簡單的中介者實現(xiàn),可以極大降低我們的應用復雜度,也能夠使得我們一路從?CRUD?到?CQRS?到?DDD?進行逐級演進。畢竟我們是生活在現(xiàn)實中的人,不能罔顧商業(yè)現(xiàn)實,純粹一味追求技術。
商業(yè)技術的演進,應該是一路持續(xù)的改革而不是來一場革命。疫情總有反復,但是我們得活著,相對輕松的活著!
總結
以上是生活随笔為你收集整理的MediatR 在.NET应用中的实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PowerToys插件扩展(类似Alfr
- 下一篇: WPF中使用资源