MediatR 解读与实战
概要
1. 中介者模式
2. Clean Architecture
3. MediatR
中介者模式
什么是中介者模式
中介者模式即对象通过中介者交互而非直接进行。
中介者模式让原本类之间的依赖转移到为了中介者与类间,由此降低程序的耦合度,并简化类之间的通信。
中介者类似消息的处理中心,对象向中介者发送请求,中介者分发请求至对应目标。获得目标响应后返回至发起者。
为什么要使用中介者模式
- 程序随功能增多产生复杂的依赖关系,某一方法可能分别依赖不同层级的对象
-
类间的通信、调用流程由于多对多的关系变得繁琐
-
利用中介者的特性获得更加合理的代码结构
Clean Architecture
什么是Clean Architecture
-
区别于传统架构自下而上的垂直结构,Clean Architecture是洋葱型的结构,只存在一个核心(Domain)层,依赖关系单向流动,但存在一对多的引用
-
区别于传统架构核心(Domain)层依赖于应用层、设施层,Clean Architecture反转了依赖关系,数据层、设施层依赖于核心(Domain)层。
一般通过向应用层添加抽象(例如接口或抽象基类)来实现。然后在应用层实现这些方法。
比如 Repository 类的实现。首先将 IRepository 接口添加到应用层。然后通过使用某一数据库(MSSQL,MYSQL)创建一个 Repository 类来在 Persistence(在应用层中,或者应用层的依赖) 中实现这个接口。而在 核心(Domain)层 中,刚才编写的逻辑仅使用 IRepository 接口,因此 核心(Domain)层 将保持独立于数据访问。
优势:
- 独立于框架/数据库- 核心(Domain)层 不依赖于外部框架,例如 Entity Framework
- 测试简单- 核心(Domain)层 中的逻辑可以独立于任何外部依赖(例如 UI、数据库、服务器)进行测试。没有外部依赖,测试编写更简单。
- 独立于 UI -Web UI 替换为console UI,或 Angular 替换为 Vue。逻辑包含在 核心(Domain)层 中,因此更改 UI 不会影响逻辑。
- 独立于任何机构- 核心(Domain)层 对外界一无所知
更快更方便地实现Clean Architecture中实现presentation/WebAPI层与Application层的分离
- 将业务逻辑分离在presentation/WebAPI之外,实现
thin controller
(如果controller中包含业务逻辑代码,没有独立于UI,假如以后要切换到console app等,就难以重用,controller中的逻辑一般也只能由请求调用) - 实现CQRS + 中介者模式,更好得复用业务逻辑相关的代码
MediatR
什么是MediatR
中介者模式实现,一种进程内消息传递框架,代码层面的消息中心,提供了中介者对象
WebApp中应用MediatR
在Presentation/Controller中调用MediatR发送/发布消息,MediatR的命令创建在Application中, 消息处理方法中调用Application
由此,Application基本等同于所有MediatR消息和消费者的实现。
直接调用与通过MediatR调用比较
功能 | 通过MediaR调用 |
---|---|
调用方法的前后进行额外处理 | 通过IPipelineBehavior实现类似中间件的功能,通过IRequestPreProcess,IRequestpostProcess来实现为某一方法调用前后附上额外运行的代码 |
调用方法触发运行一个或多个代码段 | 通过INotification实现消息的单播/多播 |
捕捉异常 | 通过IRequestExceptionHandler,IRequestExceptionAction处理执行方法中产生的异常,运行额外代码 |
直接调用方法,常常会因为需要上面的一些事件驱动的功能,多次写重复代码,破坏方法的原子性
把MediatR inject到presentation和把普通service inject到presentation有什么区别?
thin-controller
service inject到controller如下图
使用MediatR后,MediatR作为抽象的一层将依赖进一步根据具体逻辑分散
把MediatR inject到presentation/WebAPI中,借助MediatR的功能,可以更好的实践下面两点
1. 少写重复代码,尽量复用公共代码
2. 保持代码简洁,逻辑按业务/层次分散,比如WebAPI/presentation中尽可能只包含请求处理验证逻辑,显示逻辑,而appplication包含业务逻辑
MediatR.ServiceFactory
定义了一个名为MediatR.ServiceFactory 的委托,然后给这个委托加上两个扩展方法,扩展方法传入Type,返回一个Type对应的实例。
用来实例化所有的handlers, pipeline behaviors, and pre/post-processors
在注册服务时将ServiceFactory赋值为每个容器框架各自的实例化服务方法
“` C#
namespace MediatR
{
/// <summary>
/// Factory method used to resolve all services. For multiple instances, it will resolve against <see cref="IEnumerable{T}" />
/// </summary>
/// <param name="serviceType">Type of service to resolve</param>
/// <returns>An instance of type <paramref name="serviceType" /></returns>
public delegate object ServiceFactory(Type serviceType);
<pre><code>public static class ServiceFactoryExtensions
{
public static T GetInstance<T>(this ServiceFactory factory)
=> (T) factory(typeof(T));
public static IEnumerable<T> GetInstances<T>(this ServiceFactory factory)
=> (IEnumerable<T>) factory(typeof(IEnumerable<T>));
}
</code></pre>
}
<pre><code class="">在ASP .NET CORE DI 注册服务时
“` C#
//MediatR.Extensions.Microsoft.DependencyInjection/Registration/ServiceRegistrar.cs#L216
public static void AddRequiredServices(IServiceCollection services, MediatRServiceConfiguration serviceConfiguration)
{
// Use TryAdd, so any existing ServiceFactory/IMediator registration doesn’t get overriden
services.TryAddTransient<ServiceFactory>(p => p.GetService); //给ServiceFactory委托赋值
services.TryAdd(new ServiceDescriptor(typeof(IMediator), serviceConfiguration.MediatorImplementationType, serviceConfiguration.Lifetime));
services.TryAdd(new ServiceDescriptor(typeof(ISender), sp => sp.GetService<IMediator>(), serviceConfiguration.Lifetime));
services.TryAdd(new ServiceDescriptor(typeof(IPublisher), sp => sp.GetService<IMediator>(), serviceConfiguration.Lifetime));
// Use TryAddTransientExact (see below), we dó want to register our Pre/Post processor behavior, even if (a more concrete)
// registration for IPipelineBehavior<,> already exists. But only once.
services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestPostProcessorBehavior<,>));
services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionActionProcessorBehavior<,>));
services.TryAddTransientExact(typeof(IPipelineBehavior<,>), typeof(RequestExceptionProcessorBehavior<,>));
}
//netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll
namespace System
{
//
// Summary:
// Defines a mechanism for retrieving a service object; that is, an object that
// provides custom support to other objects.
public interface IServiceProvider
{
//
// Summary:
// Gets the service object of the specified type.
//
// Parameters:
// serviceType:
// An object that specifies the type of service object to get.
//
// Returns:
// A service object of type serviceType. -or- null if there is no service object
// of type serviceType.
object GetService(Type serviceType);
}
}
发送消息方法
“` C#
var handler = (RequestHandlerWrapper
t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse))));
1. 维护一个线程安全的字典静态变量缓存,减少每次通过反射实例化消费者的性能消耗
2. 先使用`Activator.CreateInstance()`创建一个实例`RequestHandlerWrapperImpl<,>`实例调用`MakeGenericType()`, 这样就可以安全转化为`RequestHandlerWrapper<TResponse>`
RequestHandlerWrapperImpl类中 handle方法实现
1. 通过容器框架的解析方法,获得对应的消费者实例
2. 通过容器框架的解析方法,获得所有已注册的消息中间件IPipelineBehavior(服务注册时压栈进入,取出reverse()后得到注册时顺序)
3. 使用Linq Aggregate逐一唤起消费者
``` C#
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))();
}
Notification消息的发布策略
通过重写MediatR.PublishCore方法来实现自定义的发布策略
默认发布策略是遍历所有消息的handlers, 逐一执行
Benifit from MediatR
- 通过MediatR + CQRS的特性,让每个逻辑方法(消息)更加原子,纯净,定义明确
- 分离presentation/WebAPI与业务逻辑。controller作用变成了“接收请求并立即分派给 MediatR”。
- 强大的事件驱动功能,增加高级别(复杂)方法的抽象能力 保持代码整洁,依赖清晰
- MediatR 更加简单地进行mock单元测试
- 借助MediatR.IPipelineBehavior实现消息中间件的功能
References
https://github.com/jbogard/MediatR
https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection
https://github.com/jasontaylordev/CleanArchitecture