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