MediatR 解读与实战

MediatR 解读与实战

概要

1. 中介者模式
2. Clean Architecture
3. MediatR

中介者模式

什么是中介者模式

中介者模式即对象通过中介者交互而非直接进行。

中介者模式让原本类之间的依赖转移到为了中介者与类间,由此降低程序的耦合度,并简化类之间的通信。

中介者类似消息的处理中心,对象向中介者发送请求,中介者分发请求至对应目标。获得目标响应后返回至发起者。

为什么要使用中介者模式
  1. 程序随功能增多产生复杂的依赖关系,某一方法可能分别依赖不同层级的对象

  2. 类间的通信、调用流程由于多对多的关系变得繁琐

  3. 利用中介者的特性获得更加合理的代码结构

Clean Architecture

什么是Clean Architecture
  1. 区别于传统架构自下而上的垂直结构,Clean Architecture是洋葱型的结构,只存在一个核心(Domain)层,依赖关系单向流动,但存在一对多的引用

  2. 区别于传统架构核心(Domain)层依赖于应用层、设施层,Clean Architecture反转了依赖关系,数据层、设施层依赖于核心(Domain)层

一般通过向应用层添加抽象(例如接口或抽象基类)来实现。然后在应用层实现这些方法。

比如 Repository 类的实现。首先将 IRepository 接口添加到应用层。然后通过使用某一数据库(MSSQL,MYSQL)创建一个 Repository 类来在 Persistence(在应用层中,或者应用层的依赖) 中实现这个接口。而在 核心(Domain)层 中,刚才编写的逻辑仅使用 IRepository 接口,因此 核心(Domain)层 将保持独立于数据访问。

优势:

  1. 独立于框架/数据库- 核心(Domain)层 不依赖于外部框架,例如 Entity Framework
  2. 测试简单- 核心(Domain)层 中的逻辑可以独立于任何外部依赖(例如 UI、数据库、服务器)进行测试。没有外部依赖,测试编写更简单。
  3. 独立于 UI -Web UI 替换为console UI,或 Angular 替换为 Vue。逻辑包含在 核心(Domain)层 中,因此更改 UI 不会影响逻辑。
  4. 独立于任何机构- 核心(Domain)层 对外界一无所知

Clean-Architecture

更快更方便地实现Clean Architecture中实现presentation/WebAPI层与Application层的分离
  1. 将业务逻辑分离在presentation/WebAPI之外,实现thin controller(如果controller中包含业务逻辑代码,没有独立于UI,假如以后要切换到console app等,就难以重用,controller中的逻辑一般也只能由请求调用)
  2. 实现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如下图
service inject到controller

使用MediatR后,MediatR作为抽象的一层将依赖进一步根据具体逻辑分散
MediatR inject到controller

把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)_requestHandlers.GetOrAdd(requestType,
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
  1. 通过MediatR + CQRS的特性,让每个逻辑方法(消息)更加原子,纯净,定义明确
  2. 分离presentation/WebAPI与业务逻辑。controller作用变成了“接收请求并立即分派给 MediatR”。
  3. 强大的事件驱动功能,增加高级别(复杂)方法的抽象能力 保持代码整洁,依赖清晰
  4. MediatR 更加简单地进行mock单元测试
  5. 借助MediatR.IPipelineBehavior实现消息中间件的功能

References

https://github.com/jbogard/MediatR
https://github.com/jbogard/MediatR.Extensions.Microsoft.DependencyInjection
https://github.com/jasontaylordev/CleanArchitecture