MediatR 知多少

引言

首先不用查字典了,詞典查無此詞。猜想是做者筆誤將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有了基本認識後,咱們來看看源碼,研究下其如何實現的。

從代碼圖中咱們能夠看到其核心的對象主要包括:

  1. IRequest Vs IRequestHandler

  2. INotification Vs INoticifaitonHandler

  3. IMediator Vs Mediator

  4. Unit

  5. IPipelineBehavior

IRequest Vs IRequestHandler

其中 IRequestINotification分別對應單播和多播消息的抽象。 對於單播消息能夠決定是否須要返回值選用不一樣的接口:

  • 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主要定義了兩個方法 SendPublish,分別用於發送消息和發佈通知。其默認實現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>();

這裏面其又引入了兩個包裝類: RequestHandlerWrapperNotificationHandlerWrapper。這兩個包裝類的做用就是用來傳遞 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的默認實現: RequestPreProcessorBehaviorRequestPostProcessorBehavior分別用來處理全部實現 IRequestPreProcessorIRequestPostProcessor接口定義的管道行爲。

而處理管道是如何構建的呢?咱們來看下 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))();    }}

就這樣一個簡單的函數,涉及的知識點還真很多,說實話我花了很多時間來理清這個邏輯。 那都涉及到哪些知識點呢?咱們一個一個的來理一理。

  1. C# 7.0的新特性 - 局部函數

  2. C# 6.0的新特性 - 表達式形式的成員函數

  3. Linq高階函數 - Aggregate

  4. 匿名委託

  5. 構造委託函數鏈

關於第一、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

和函數參數進行一一對應:

  1. seed : 1

  2. Func  func : (total, next) => total + next

  3. 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索