引言
首先不用查字典了,詞典查無此詞。猜測是作者筆誤將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.
.NET中的簡單中介者樣式實現,一種行程內訊息傳遞機制(無其他外部依賴)。 支援以同步或非同步的形式進行請求/響應,命令,查詢,通知和事件的訊息傳遞,並透過C#泛型支援訊息的智慧排程。
如上所述,其核心是一個中介者樣式的.NET實現,其目的是訊息傳送和訊息處理的解耦。它支援以單播和多播形式使用同步或非同步的樣式來釋出訊息,建立和偵聽事件。
中介者樣式
既然是對中介者樣式的一種實現,那麼我們就有必要簡要介紹下中介者這個設計樣式,以便後續展開。
中介者樣式:用一個中介物件封裝一系列的物件互動,中介者使各物件不需要顯示地相互作用,從而使耦合鬆散,而且可以獨立地改變它們之間的互動。
看上面的官方定義可能還是有點繞,那麼下麵這張圖應該能幫助你對中介者樣式有個直觀瞭解。
使用中介樣式,物件之間的互動將封裝在中介物件中。物件不再直接相互互動(解耦),而是透過中介進行互動。這減少了物件之間的依賴性,從而減少了耦合。
那其優缺點也在圖中很容易看出:
優點:中介者樣式的優點就是減少類間的依賴,把原有的一對多的依賴變成了一對一的依賴,同事類只依賴中介者,減少了依賴,當然同時也降低了類間的耦合
缺點:中介者樣式的缺點就是中介者會膨脹得很大,而且邏輯複雜,原本N個物件直接的相互依賴關係轉換為中介者和同事類的依賴關係,同事類越多,中介者的邏輯就越複雜。
Hello MeidatR
在開始之前,我們先來瞭解下其基本用法。
單播訊息傳輸
單播訊息傳輸,也就是一對一的訊息傳遞,一個訊息對應一個訊息處理。其透過 IRequest
來抽象單播訊息,用 IRequestHandler
進行訊息處理。
//構建 訊息請求
public class Ping : IRequest { }
//構建 訊息處理
public class PingHandler : IRequestHandler<Ping, string> {
public Task 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 { }
//構建 訊息處理器1
public class Pong1 : INotificationHandler<Ping> {
public Task Handle(Ping notification, CancellationToken cancellationToken) {
Debug.WriteLine("Pong 1");
return Task.CompletedTask;
}
}
//構建 訊息處理器2
public 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
來代表無傳回的情況。
///
/// 代表無需傳回值的請求
///
public interface IRequest : IRequest<Unit> { }
///
/// 代表有傳回值的請求
///
/// Response type
public interface IRequest<out TResponse> : IBaseRequest { }
///
/// Allows for generic type constraints of objects implementing IRequest or IRequest{TResponse}
///
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
- 匿名委託
- 構造委託函式鏈
關於第1、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的累加之和,再將結果乘以2
var sum = nums.Aggregate(1, (total, next) => total + next, result => result * 2);// 相當於 (((1+2)+3)+4)*2=20
Console.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 實現
已傳送
朋友將在看一看看到
分享你的想法…
分享想法到看一看