MediatR 知多少 - 简书
引言
首先不用查字典了,詞典查無(wú)此詞。猜測(cè)是作者筆誤將Mediator寫(xiě)成MediatR了。廢話少說(shuō),轉(zhuǎn)入正題。
先來(lái)簡(jiǎn)單了解下這個(gè)開(kāi)源項(xiàng)目MediatR(作者Jimmy Bogard,也是開(kāi)源項(xiàng)目AutoMapper的創(chuàng)建者,在此表示膜拜):
Simple mediator implementation in .NET. In-process messaging with no dependencies. Supports request/response, commands, queries, notifications and events, synchronous and async with intelligent dispatching via C# generic variance.
.NET中的簡(jiǎn)單中介者模式實(shí)現(xiàn),一種進(jìn)程內(nèi)消息傳遞機(jī)制(無(wú)其他外部依賴(lài))。 支持以同步或異步的形式進(jìn)行請(qǐng)求/響應(yīng),命令,查詢,通知和事件的消息傳遞,并通過(guò)C#泛型支持消息的智能調(diào)度。
如上所述,其核心是一個(gè)中介者模式的.NET實(shí)現(xiàn),其目的是消息發(fā)送和消息處理的解耦。它支持以單播和多播形式使用同步或異步的模式來(lái)發(fā)布消息,創(chuàng)建和偵聽(tīng)事件。
中介者模式
既然是對(duì)中介者模式的一種實(shí)現(xiàn),那么我們就有必要簡(jiǎn)要介紹下中介者這個(gè)設(shè)計(jì)模式,以便后續(xù)展開(kāi)。
中介者模式類(lèi)圖
中介者模式:用一個(gè)中介對(duì)象封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯示地相互作用,從而使耦合松散,而且可以獨(dú)立地改變它們之間的交互。
看上面的官方定義可能還是有點(diǎn)繞,那么下面這張圖應(yīng)該能幫助你對(duì)中介者模式有個(gè)直觀了解。
使用中介模式,對(duì)象之間的交互將封裝在中介對(duì)象中。對(duì)象不再直接相互交互(解耦),而是通過(guò)中介進(jìn)行交互。這減少了對(duì)象之間的依賴(lài)性,從而減少了耦合。
那其優(yōu)缺點(diǎn)也在圖中很容易看出:
優(yōu)點(diǎn):中介者模式的優(yōu)點(diǎn)就是減少類(lèi)間的依賴(lài),把原有的一對(duì)多的依賴(lài)變成了一對(duì)一的依賴(lài),同事類(lèi)只依賴(lài)中介者,減少了依賴(lài),當(dāng)然同時(shí)也降低了類(lèi)間的耦合
缺點(diǎn):中介者模式的缺點(diǎn)就是中介者會(huì)膨脹得很大,而且邏輯復(fù)雜,原本N個(gè)對(duì)象直接的相互依賴(lài)關(guān)系轉(zhuǎn)換為中介者和同事類(lèi)的依賴(lài)關(guān)系,同事類(lèi)越多,中介者的邏輯就越復(fù)雜。
Hello MeidatR
在開(kāi)始之前,我們先來(lái)了解下其基本用法。
單播消息傳輸
單播消息傳輸,也就是一對(duì)一的消息傳遞,一個(gè)消息對(duì)應(yīng)一個(gè)消息處理。其通過(guò)IRequest來(lái)抽象單播消息,用IRequestHandler進(jìn)行消息處理。
//構(gòu)建 消息請(qǐng)求 public class Ping : IRequest<string> { } //構(gòu)建 消息處理 public class PingHandler : IRequestHandler<Ping, string> {public Task<string> Handle(Ping request, CancellationToken cancellationToken) {return Task.FromResult("Pong");} } //發(fā)送 請(qǐng)求 var response = await mediator.Send(new Ping()); Debug.WriteLine(response); // "Pong"多播消息傳輸
多播消息傳輸,也就是一對(duì)多的消息傳遞,一個(gè)消息對(duì)應(yīng)多個(gè)消息處理。其通過(guò)INotification來(lái)抽象多播消息,對(duì)應(yīng)的消息處理類(lèi)型為INotificationHandler。
//構(gòu)建 通知消息 public class Ping : INotification { } //構(gòu)建 消息處理器1 public class Pong1 : INotificationHandler<Ping> {public Task Handle(Ping notification, CancellationToken cancellationToken) {Debug.WriteLine("Pong 1");return Task.CompletedTask;} } //構(gòu)建 消息處理器2 public class Pong2 : INotificationHandler<Ping> {public Task Handle(Ping notification, CancellationToken cancellationToken) {Debug.WriteLine("Pong 2");return Task.CompletedTask;} }//發(fā)布消息 await mediator.Publish(new Ping());源碼解析
對(duì)MediatR有了基本認(rèn)識(shí)后,我們來(lái)看看源碼,研究下其如何實(shí)現(xiàn)的。
類(lèi)圖從代碼圖中我們可以看到其核心的對(duì)象主要包括:
IRequest Vs IRequestHandler
其中IRequest和INotification分別對(duì)應(yīng)單播和多播消息的抽象。
對(duì)于單播消息可以決定是否需要返回值選用不同的接口:
- IRequest<T> - 有返回值
- IRequest - 無(wú)返回值
這里就不得不提到其中巧妙的設(shè)計(jì),通過(guò)引入結(jié)構(gòu)類(lèi)型Unit來(lái)代表無(wú)返回的情況。
/// <summary> /// 代表無(wú)需返回值的請(qǐng)求 /// </summary> public interface IRequest : IRequest<Unit> { }/// <summary> /// 代表有返回值的請(qǐng)求 /// </summary> /// <typeparam name="TResponse">Response type</typeparam> public interface IRequest<out TResponse> : IBaseRequest { }/// <summary> /// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse} /// </summary> public interface IBaseRequest { }同樣對(duì)于IRequestHandler也是通過(guò)結(jié)構(gòu)類(lèi)型Unit來(lái)處理不需要返回值的情況。
public interface IRequestHandler<in TRequest, TResponse>where TRequest : IRequest<TResponse> {Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken); }public interface IRequestHandler<in TRequest> : IRequestHandler<TRequest, Unit>where TRequest : IRequest<Unit> { }從上面我們可以看出定義了一個(gè)方法名為Handle返回值為T(mén)ask的包裝類(lèi)型,而因此賦予了其具有以同步和異步的方式進(jìn)行消息處理的能力。我們?cè)倏匆幌缕湟援惒椒绞竭M(jìn)行消息處理(無(wú)返回值)的默認(rèn)實(shí)現(xiàn)AsyncRequestHandler:
public abstract class AsyncRequestHandler<TRequest> : IRequestHandler<TRequest>where TRequest : IRequest {async Task<Unit> IRequestHandler<TRequest, Unit>.Handle(TRequest request, CancellationToken cancellationToken){await Handle(request, cancellationToken).ConfigureAwait(false);return Unit.Value;}protected abstract Task Handle(TRequest request, CancellationToken cancellationToken); }從上面的代碼來(lái)看,我們很容易看出這是裝飾模式的實(shí)現(xiàn)方式,是不是很巧妙的解決了無(wú)需返回值的場(chǎng)景。
最后我們來(lái)看下結(jié)構(gòu)類(lèi)型Unit的定義:
public struct Unit : IEquatable<Unit>, IComparable<Unit>, IComparable {public static readonly Unit Value = new Unit();public static readonly Task<Unit> Task = System.Threading.Tasks.Task.FromResult(Value);// some other code }IMediator Vs Mediator
MediatR 類(lèi)圖IMediator主要定義了兩個(gè)方法Send和Publish,分別用于發(fā)送消息和發(fā)布通知。其默認(rèn)實(shí)現(xiàn)Mediator中定義了兩個(gè)集合,分別用來(lái)保存請(qǐng)求與請(qǐng)求處理的映射關(guān)系。
//Mediator.cs //保存request和requesthandler的映射關(guān)系,1對(duì)1。 private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>(); //保存notification與notificationhandler的映射關(guān)系, private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();這里面其又引入了兩個(gè)包裝類(lèi):RequestHandlerWrapper和NotificationHandlerWrapper。這兩個(gè)包裝類(lèi)的作用就是用來(lái)傳遞ServiceFactory委托進(jìn)行依賴(lài)解析。
所以說(shuō)Mediator借助public delegate object ServiceFactory(Type serviceType);完成對(duì)Ioc容器的一層抽象。這樣就可以對(duì)接任意你喜歡用的Ioc容器,比如:Autofac、Windsor或ASP.NET Core默認(rèn)的Ioc容器,只需要在注冊(cè)IMediator時(shí)指定ServiceFactory類(lèi)型的委托即可,比如ASP.NET Core中的做法:
在使用ASP.NET Core提供的原生Ioc容器有些問(wèn)題:Service registration crashes when registering generic handlers
IPipelineBehavior
處理管道MeidatR支持按需配置請(qǐng)求管道進(jìn)行消息處理。即支持在請(qǐng)求處理前和請(qǐng)求處理后添加額外行為。僅需實(shí)現(xiàn)以下兩個(gè)接口,并注冊(cè)到Ioc容器即可。
- IRequestPreProcessor<in TRequest> 請(qǐng)求處理前接口
- IRequestPostProcessor<in TRequest, in TResponse> 請(qǐng)求處理后接口
其中IPipelineBehavior的默認(rèn)實(shí)現(xiàn):RequestPreProcessorBehavior和RequestPostProcessorBehavior分別用來(lái)處理所有實(shí)現(xiàn)IRequestPreProcessor和IRequestPostProcessor接口定義的管道行為。
而處理管道是如何構(gòu)建的呢?我們來(lái)看下RequestHandlerWrapperImpl的具體實(shí)現(xiàn):
internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse>where TRequest : IRequest<TResponse> {public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken,ServiceFactory serviceFactory){Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse(serviceFactory).Handle((TRequest) request, cancellationToken);return serviceFactory.GetInstances<IPipelineBehavior<TRequest, TResponse().Reverse().Aggregate((RequestHandlerDelegate<TResponse>) Handler, (next, pipeline) => () => pipeline.Handle((TRequest)request, cancellationToken, next))();} }就這樣一個(gè)簡(jiǎn)單的函數(shù),涉及的知識(shí)點(diǎn)還真不少,說(shuō)實(shí)話我花了不少時(shí)間來(lái)理清這個(gè)邏輯。
那都涉及到哪些知識(shí)點(diǎn)呢?我們一個(gè)一個(gè)的來(lái)理一理。
關(guān)于第1、2個(gè)知識(shí)點(diǎn),請(qǐng)看下面這段代碼:
public delegate int SumDelegate();//定義委托 public static void Main() {//局部函數(shù)(在函數(shù)內(nèi)部定義函數(shù))//表達(dá)式形式的成員函數(shù), 相當(dāng)于 int Sum() { return 1 + 2;}int Sum() => 1 + 2;var sumDelegate = (SumDelegate)Sum;//轉(zhuǎn)換為委托Console.WriteLine(sumDelegate());//委托調(diào)用,輸出:3 }再看第4個(gè)知識(shí)點(diǎn),匿名委托:
public delegate int SumDelegate();SumDelegate delegater1 = delegate(){ return 1+2; } //也相當(dāng)于 SumDelegate delegater2 => 1+2;下面再來(lái)介紹一下Aggregate這個(gè)Linq高階函數(shù)。Aggregate是對(duì)一個(gè)集合序列進(jìn)行累加操作,通過(guò)指定初始值,累加函數(shù),以及結(jié)果處理函數(shù)完成計(jì)算。
函數(shù)定義:
public static TResult Aggregate<TSource,TAccumulate,TResult> (this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate,TSource,TAccumulate> func, Func<TAccumulate,TResult> resultSelector);根據(jù)函數(shù)定義我們來(lái)寫(xiě)個(gè)簡(jiǎn)單的demo:
var nums = Enumerable.Range(2, 3);//[2,3,4] // 計(jì)算1到5的累加之和,再將結(jié)果乘以2 var sum = nums.Aggregate(1, (total, next) => total + next, result => result * 2);// 相當(dāng)于 (((1+2)+3)+4)*2=20 Console.WriteLine(sum);//20和函數(shù)參數(shù)進(jìn)行一一對(duì)應(yīng):
基于上面的認(rèn)識(shí),我們?cè)賮?lái)回過(guò)頭梳理一下RequestHandlerWrapperImpl。
其主要是借助委托:public delegate Task<TResponse> RequestHandlerDelegate<TResponse>();來(lái)構(gòu)造委托函數(shù)鏈來(lái)構(gòu)建處理管道。
對(duì)Aggregate函數(shù)了解后,我們就不難理解處理管道的構(gòu)建了。請(qǐng)看下圖中的代碼解讀:
請(qǐng)求處理管道代碼解讀 構(gòu)建流程解析那如何保證先執(zhí)行IRequestPreProcessor再執(zhí)行IRequestPostProcessor呢?
就是在注冊(cè)到Ioc容器時(shí)必須保證順序,先注冊(cè)IRequestPreProcessor再注冊(cè)IRequestPostProcessor。(這一點(diǎn)很重要!!!)
看到這里有沒(méi)有想到ASP.NET Core中請(qǐng)求管道中中間件的構(gòu)建呢?是不是很像俄羅斯套娃?先由內(nèi)而外構(gòu)建管道,再由外而內(nèi)執(zhí)行!
至此,MediatR的實(shí)現(xiàn)思路算是理清了。
應(yīng)用場(chǎng)景
如文章開(kāi)頭提到:MediatR是一種進(jìn)程內(nèi)消息傳遞機(jī)制。 支持以同步或異步的形式進(jìn)行請(qǐng)求/響應(yīng),命令,查詢,通知和事件的消息傳遞,并通過(guò)C#泛型支持消息的智能調(diào)度。
那么我們就應(yīng)該明白,其核心是消息的解耦。因?yàn)槲覀儙缀醵际窃谂c消息打交道,那因此它的應(yīng)用場(chǎng)景就很廣泛,比如我們可以基于MediatR實(shí)現(xiàn)CQRS、EventBus等。
另外,還有一種應(yīng)用場(chǎng)景:我們知道借助依賴(lài)注入的好處是,就是解除依賴(lài),但我們又不得不思考一個(gè)問(wèn)題,隨著業(yè)務(wù)邏輯復(fù)雜度的增加,構(gòu)造函數(shù)可能要注入更多的服務(wù),當(dāng)注入的依賴(lài)太多時(shí),其會(huì)導(dǎo)致構(gòu)造函數(shù)膨脹。比如:
public DashboardController(ICustomerRepository customerRepository,IOrderService orderService,ICustomerHistoryRepository historyRepository,IOrderRepository orderRepository,IProductRespoitory productRespoitory,IRelatedProductsRepository relatedProductsRepository,ISupportService supportService,ILog logger)如果借助MediatR進(jìn)行改造,也許僅需注入IMediatR就可以了。
public DashboardController(IMediatR mediatr)總結(jié)
看到這里,也許你應(yīng)該明白MediatR實(shí)質(zhì)上并不是嚴(yán)格意義上的中介者模式實(shí)現(xiàn),我更傾向于其是基于Ioc容器的一層抽象,根據(jù)請(qǐng)求定位相應(yīng)的請(qǐng)求處理器進(jìn)行消息處理,也就是服務(wù)定位。
那到這里似乎也恍然大悟MediatR這個(gè)筆誤可能是有意為之了。序員,你怎么看?
參考資料:
CQRS/MediatR implementation patterns
MediatR when and why I should use it? vs 2017 webapi
ABP CQRS 實(shí)現(xiàn)案例:基于 MediatR 實(shí)現(xiàn)
轉(zhuǎn)載于:https://www.cnblogs.com/lonelyxmas/p/11001982.html
總結(jié)
以上是生活随笔為你收集整理的MediatR 知多少 - 简书的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Django-你想知道的都在这里
- 下一篇: C++ replace replace