引言
首先不用查字典了,詞典查無此詞。猜想是做者筆誤將Mediator寫成MediatR了。廢話少說,轉入正題。設計模式
先來簡單瞭解下這個開源項目MediatR(做者Jimmy Bogard,也是開源項目AutoMapper的建立者,在此表示膜拜):微信
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.app
.NET中的簡單中介者模式實現,一種進程內消息傳遞機制(無其餘外部依賴)。 支持以同步或異步的形式進行請求/響應,命令,查詢,通知和事件的消息傳遞,並經過C#泛型支持消息的智能調度。異步
如上所述,其核心是一箇中介者模式的.NET實現,其目的是消息發送和消息處理的解耦。它支持以單播和多播形式使用同步或異步的模式來發布消息,建立和偵聽事件。async
中介者模式
既然是對中介者模式的一種實現,那麼咱們就有必要簡要介紹下中介者這個設計模式,以便後續展開。ide
中介者模式:用一箇中介對象封裝一系列的對象交互,中介者使各對象不須要顯示地相互做用,從而使耦合鬆散,並且能夠獨立地改變它們之間的交互。函數
看上面的官方定義可能仍是有點繞,那麼下面這張圖應該能幫助你對中介者模式有個直觀瞭解。微服務
使用中介模式,對象之間的交互將封裝在中介對象中。對象再也不直接相互交互(解耦),而是經過中介進行交互。這減小了對象之間的依賴性,從而減小了耦合。flex
那其優缺點也在圖中很容易看出:this
優勢:中介者模式的優勢就是減小類間的依賴,把原有的一對多的依賴變成了一對一的依賴,同事類只依賴中介者,減小了依賴,固然同時也下降了類間的耦合
缺點:中介者模式的缺點就是中介者會膨脹得很大,並且邏輯複雜,本來N個對象直接的相互依賴關係轉換爲中介者和同事類的依賴關係,同事類越多,中介者的邏輯就越複雜。
Hello MeidatR
在開始以前,咱們先來了解下其基本用法。
單播消息傳輸
單播消息傳輸,也就是一對一的消息傳遞,一個消息對應一個消息處理。其經過 IRequest
來抽象單播消息,用 IRequestHandler
進行消息處理。
//構建 消息請求public class Ping : IRequest<string> { }//構建 消息處理public class PingHandler : IRequestHandler<Ping, string> { public Task<string> Handle(Ping request, CancellationToken cancellationToken) { return Task.FromResult("Pong"); }}//發送 請求var response = await mediator.Send(new Ping());Debug.WriteLine(response); // "Pong"
多播消息傳輸
多播消息傳輸,也就是一對多的消息傳遞,一個消息對應多個消息處理。其經過 INotification
來抽象多播消息,對應的消息處理類型爲 INotificationHandler
。
//構建 通知消息public class Ping : INotification { }//構建 消息處理器1public class Pong1 : INotificationHandler<Ping> { public Task Handle(Ping notification, CancellationToken cancellationToken) { Debug.WriteLine("Pong 1"); return Task.CompletedTask; }}//構建 消息處理器2public class Pong2 : INotificationHandler<Ping> { public Task Handle(Ping notification, CancellationToken cancellationToken) { Debug.WriteLine("Pong 2"); return Task.CompletedTask; }}//發佈消息await mediator.Publish(new Ping());
源碼解析
對MediatR有了基本認識後,咱們來看看源碼,研究下其如何實現的。
從代碼圖中咱們能夠看到其核心的對象主要包括:
IRequest Vs IRequestHandler
INotification Vs INoticifaitonHandler
IMediator Vs Mediator
Unit
IPipelineBehavior
IRequest Vs IRequestHandler
其中 IRequest
和 INotification
分別對應單播和多播消息的抽象。 對於單播消息能夠決定是否須要返回值選用不一樣的接口:
IRequest
- 有返回值 IRequest - 無返回值
這裏就不得不提到其中巧妙的設計,經過引入結構類型 Unit
來表明無返回的狀況。
/// <summary>/// 表明無需返回值的請求/// </summary>public interface IRequest : IRequest<Unit> { }/// <summary>/// 表明有返回值的請求/// </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 { }
一樣對於 IRequestHandler
也是經過結構類型 Unit
來處理不須要返回值的狀況。
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>{}
從上面咱們能夠看出定義了一個方法名爲 Handle
返回值爲 Task
的包裝類型,而所以賦予了其具備以同步和異步的方式進行消息處理的能力。咱們再看一下其以異步方式進行消息處理(無返回值)的默認實現 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);}
從上面的代碼來看,咱們很容易看出這是裝飾模式的實現方式,是否是很巧妙的解決了無需返回值的場景。
最後咱們來看下結構類型 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
IMediator
主要定義了兩個方法 Send
和 Publish
,分別用於發送消息和發佈通知。其默認實現Mediator中定義了兩個集合,分別用來保存請求與請求處理的映射關係。
//Mediator.cs//保存request和requesthandler的映射關係,1對1。private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();//保存notification與notificationhandler的映射關係,private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();
這裏面其又引入了兩個包裝類: RequestHandlerWrapper
和 NotificationHandlerWrapper
。這兩個包裝類的做用就是用來傳遞 ServiceFactory
委託進行依賴解析。
因此說 Mediator
藉助 publicdelegateobjectServiceFactory(TypeserviceType);
完成對Ioc容器的一層抽象。這樣就能夠對接任意你喜歡用的Ioc容器,好比:Autofac、Windsor或ASP.NET Core默認的Ioc容器,只須要在註冊 IMediator
時指定 ServiceFactory
類型的委託便可,好比ASP.NET Core中的作法:
在使用ASP.NET Core提供的原生Ioc容器有些問題:Service registration crashes when registering generic handlers
IPipelineBehavior
MeidatR支持按需配置請求管道進行消息處理。即支持在請求處理前和請求處理後添加額外行爲。僅需實現如下兩個接口,並註冊到Ioc容器便可。
IRequestPreProcessor
請求處理前接口 IRequestPostProcessor
請求處理後接口
其中 IPipelineBehavior
的默認實現: RequestPreProcessorBehavior
和 RequestPostProcessorBehavior
分別用來處理全部實現 IRequestPreProcessor
和 IRequestPostProcessor
接口定義的管道行爲。
而處理管道是如何構建的呢?咱們來看下 RequestHandlerWrapperImpl
的具體實現:
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))(); }}
就這樣一個簡單的函數,涉及的知識點還真很多,說實話我花了很多時間來理清這個邏輯。 那都涉及到哪些知識點呢?咱們一個一個的來理一理。
C# 7.0的新特性 - 局部函數
C# 6.0的新特性 - 表達式形式的成員函數
Linq高階函數 -
Aggregate
匿名委託
構造委託函數鏈
關於第一、2個知識點,請看下面這段代碼:
public delegate int SumDelegate();//定義委託public static void Main(){ //局部函數(在函數內部定義函數) //表達式形式的成員函數, 至關於 int Sum() { return 1 + 2;} int Sum() => 1 + 2; var sumDelegate = (SumDelegate)Sum;//轉換爲委託 Console.WriteLine(sumDelegate());//委託調用,輸出:3}
再看第4個知識點,匿名委託:
public delegate int SumDelegate();SumDelegate delegater1 = delegate(){ return 1+2; }//也至關於SumDelegate delegater2 => 1+2;
下面再來介紹一下 Aggregate
這個Linq高階函數。 Aggregate
是對一個集合序列進行累加操做,經過指定初始值,累加函數,以及結果處理函數完成計算。
函數定義:
public static TResult Aggregate<TSource,TAccumulate,TResult>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate,TSource,TAccumulate> func, Func<TAccumulate,TResult> resultSelector);
根據函數定義咱們來寫個簡單的demo:
var nums = Enumerable.Range(2, 3);//[2,3,4]// 計算1到5的累加之和,再將結果乘以2var sum = nums.Aggregate(1, (total, next) => total + next, result => result * 2);// 至關於 (((1+2)+3)+4)*2=20Console.WriteLine(sum);//20
和函數參數進行一一對應:
seed : 1
Func
func : (total, next) => total + next Func
resultSelector : result => result * 2
基於上面的認識,咱們再來回過頭梳理一下 RequestHandlerWrapperImpl
。 其主要是藉助委託: publicdelegateTask<TResponse>RequestHandlerDelegate<TResponse>();
來構造委託函數鏈來構建處理管道。
對 Aggregate
函數了解後,咱們就不難理解處理管道的構建了。請看下圖中的代碼解讀:
那如何保證先執行 IRequestPreProcessor
再執行 IRequestPostProcessor
呢? 就是在註冊到Ioc容器時必須保證順序,先註冊 IRequestPreProcessor
再註冊 IRequestPostProcessor
。(這一點很重要!!!)
看到這裏有沒有想到ASP.NET Core中請求管道中中間件的構建呢?是否是很像俄羅斯套娃?先由內而外構建管道,再由外而內執行!
至此,MediatR的實現思路算是理清了。
應用場景
如文章開頭提到:MediatR是一種進程內消息傳遞機制。 支持以同步或異步的形式進行請求/響應,命令,查詢,通知和事件的消息傳遞,並經過C#泛型支持消息的智能調度。
那麼咱們就應該明白,其核心是消息的解耦。由於咱們幾乎都是在與消息打交道,那所以它的應用場景就很普遍,好比咱們能夠基於MediatR實現CQRS、EventBus等。
另外,還有一種應用場景:咱們知道藉助依賴注入的好處是,就是解除依賴,但咱們又不得不思考一個問題,隨着業務邏輯複雜度的增長,構造函數可能要注入更多的服務,當注入的依賴太多時,其會致使構造函數膨脹。好比:
public DashboardController( ICustomerRepository customerRepository, IOrderService orderService, ICustomerHistoryRepository historyRepository, IOrderRepository orderRepository, IProductRespoitory productRespoitory, IRelatedProductsRepository relatedProductsRepository, ISupportService supportService, ILog logger )
若是藉助 MediatR
進行改造,也許僅需注入 IMediatR
就能夠了。
public DashboardController(IMediatR mediatr)
總結
看到這裏,也許你應該明白MediatR實質上並非嚴格意義上的中介者模式實現,我更傾向於其是基於Ioc容器的一層抽象,根據請求定位相應的請求處理器進行消息處理,也就是服務定位。 那到這裏彷佛也恍然大悟MediatR這個筆誤多是有意爲之了。序員,你怎麼看?
參考資料:
CQRS/MediatR implementation patterns
MediatR when and why I should use it?
ABP CQRS 實現案例:基於 MediatR 實現
本文分享自微信公衆號 - 微服務知多少(dotnet-microservice)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。