学习开源项目——MediatR

929 阅读11分钟

Mediat是github上的一个开源项目,项目地址:github.com/jbogard/Med…

聊一聊

学习一个项目,最关键的就是要搞懂几件事情:MediatR是什么、核心内容是什么、能用来做什么、使用场景是什么。带着这几个问题去看源码,只要源码看懂了,那么这个项目差不多也就掌握了。

看懂一个项目,还有一个非常核心的前提:面向对象的思想
那些开发大牛,他们都有着极其抽象的抽象思想和面向对象的思想,他们对面向对象的分析和设计模式早已谙熟于心。所以对于每一位开发者来说,面向对象的思想设计模式都应该熟悉,并且牢牢掌握。

中介者模式

开始分析MediatR项目之前,我们先来分享一个设计模式———中介者模式(Mediator)。眼神好的童子们可能会发现MediatR和Mediator两个单词非常的相似。其实并没有MediatR这个单词的。但是MediatR项目确实有中介者模式的思想。所以下面我们先来看看什么是中介者模式。

先上UML图,这是《大话设计模式》一书中的插图。(本人拜读了好多遍,本人的OOP思想也是从本书受到启发的 :laughing: )

总的来说,中介者模式中有几种角色:

  • Mediator(抽象中介者):通过定义一个接口与各个同事对象进行通信。
  • ConcreteMediator(具体中介者):是抽象中介者的具体实现,需要知道所有具体同事类。
  • Colleague(抽象同事类):定义了通过中介者与其他同事进行通信的接口
  • ConcreteColleague(具体同事类):抽象同事接口的实现类,当一个同事对象需要与其他同事机型通信时,将统一通过具体中介者来进行通信。

这几种角色也不是绝对都需要的。比如,Mediator和Colleague,可以视具体情况省略从而简化。

中介者模式概念:用一个中介对象(ConcreteMediator)来封装一系列的对象(ConcreteColleague1、ConcreteColleague2等)交互。中介者(ConcreteMediator)使各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间地交互

好了,中介者模式就介绍这么多了。它的具体案例自己可以参考网页上或者书本中的,本人不再提供。

接下来,我们的硬菜开始上场。。。

MediatR

MediatR是什么

MediatR是一款基于中介者模式的思想而实现的.NET库,主要是为了解决进程内(in-process,进程内指的是同一个进程,而不是多个进程之间的)消息发送与消息处理过程之间的耦合问题。例如,一个Controller需要调用相应的Repository进行业务逻辑处理,普通情况下,可以直接在Controller注入Repository,而在使用MediatR的情况下,可以先注入MediatR,再通过MediatR发送给Repository,减少了耦合。它可跨平台,支持.NET 4.6.1+以及netstandard2.0。

MediatR核心内容是什么

MediatR源码的核心是IMediator接口,对应中介者模式中的Mediator(抽象中介者)。
这个接口定义了四个方法:

    /// <summary>
    /// Defines a mediator to encapsulate request/response and publishing interaction patterns
    /// (抽象中介者。定义一个中介器来封装请求/响应和发布交互模式)
    /// </summary>
    public interface IMediator
    {
        /// <summary>
        /// Asynchronously send a request to a single handler
        /// (异步发送一个请求,到一个单一的处理程序)
        /// </summary>
        /// <typeparam name="TResponse">Response type</typeparam>
        /// <param name="request">Request object</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>A task that represents the send operation. The task result contains the handler response</returns>
        Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default);

        /// <summary>
        /// Asynchronously send an object request to a single handler via dynamic dispatch
        /// (通过动态调度将对象请求异步发送到单个处理程序)
        /// </summary>
        /// <param name="request">Request object</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>A task that represents the send operation. The task result contains the type erased handler response</returns>
        Task<object?> Send(object request, CancellationToken cancellationToken = default);

        /// <summary>
        /// Asynchronously send a notification to multiple handlers
        /// (异步发布一个通知,到多个处理程序)
        /// </summary>
        /// <param name="notification">Notification object</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>A task that represents the publish operation.</returns>
        Task Publish(object notification, CancellationToken cancellationToken = default);

        /// <summary>
        /// Asynchronously send a notification to multiple handlers
        /// (异步发布一个通知,到多个处理程序)
        /// </summary>
        /// <param name="notification">Notification object</param>
        /// <param name="cancellationToken">Optional cancellation token</param>
        /// <returns>A task that represents the publish operation.</returns>
        Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
            where TNotification : INotification;
    }	

MediatR的作者也帮助我们实现了这个接口,实现类是Mediator。对应中介者模式中的ConcreteMediator(具体中介者)。当然我们自己也可以去实现IMediator接口,实现属于我们自己的具体中介者。这就是面向接口开发的好处,也映衬了OOD中的依赖倒置原则。 拜读一下源码:

	/// <summary>
    /// Default mediator implementation relying on single- and multi instance delegates for resolving handlers.
    /// (具体中介者。实现抽象接口,它需要知道所有具体的同事类,并从具体同事类接收消息,向具体同事类发出命令)
    /// </summary>
    public class Mediator : IMediator
    {
        private readonly ServiceFactory _serviceFactory;
        private static readonly ConcurrentDictionary<Type, object> _requestHandlers = new ConcurrentDictionary<Type, object>();
        private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new ConcurrentDictionary<Type, NotificationHandlerWrapper>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Mediator"/> class.
        /// (初始化一个新的中介者类对象实例)
        /// </summary>
        /// <param name="serviceFactory">The single instance factory.</param>
        public Mediator(ServiceFactory serviceFactory)
        {
            _serviceFactory = serviceFactory;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TResponse">响应类型</typeparam>
        /// <param name="request">具体同事类</param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<TResponse> Send<TResponse>(IRequest<TResponse> request, CancellationToken cancellationToken = default)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            var requestType = request.GetType();//具体同事类的类型

            Func<Type, object> valueFactory = t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, typeof(TResponse)));
            var handler = (RequestHandlerWrapper<TResponse>) _requestHandlers.GetOrAdd(requestType, valueFactory);

            return handler.Handle(request, cancellationToken, _serviceFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="request">具体同事类</param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<object?> Send(object request, CancellationToken cancellationToken = default)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }
            var requestType = request.GetType();//获取具体同事类的类型
            var requestInterfaceType = requestType
                .GetInterfaces()//获取具体同事类继承的接口
                .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRequest<>));//从具体同事类继承的接口中,筛选泛型类型、且泛型类型是IRequest<>
            var isValidRequest = requestInterfaceType != null;//true

            if (!isValidRequest)//判断具体同事类类型是否存在
            {
                throw new ArgumentException($"{nameof(request)} does not implement ${nameof(IRequest)}");
            }

            var responseType = requestInterfaceType!.GetGenericArguments()[0];//从具体同事类request的泛型参数中,获取响应类型

            var handler = _requestHandlers.GetOrAdd(requestType,
                t => Activator.CreateInstance(typeof(RequestHandlerWrapperImpl<,>).MakeGenericType(requestType, responseType)));

            // call via dynamic dispatch to avoid calling through reflection for performance reasons
            return ((RequestHandlerBase) handler).Handle(request, cancellationToken, _serviceFactory);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TNotification"></typeparam>
        /// <param name="notification"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
             where TNotification : INotification
        {
            if (notification == null)
            {
                throw new ArgumentNullException(nameof(notification));
            }

            return PublishNotification(notification, cancellationToken);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="notification"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task Publish(object notification, CancellationToken cancellationToken = default)
        {
            if (notification == null)
            {
                throw new ArgumentNullException(nameof(notification));
            }
            if (notification is INotification instance)
            {
                return PublishNotification(instance, cancellationToken);
            }

            throw new ArgumentException($"{nameof(notification)} does not implement ${nameof(INotification)}");
        }

        /// <summary>
        /// Override in a derived class to control how the tasks are awaited. By default the implementation is a foreach and await of each handler
        /// </summary>
        /// <param name="allHandlers">Enumerable of tasks representing invoking each notification handler</param>
        /// <param name="notification">The notification being published</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>A task representing invoking all handlers</returns>
        protected virtual async Task PublishCore(IEnumerable<Func<INotification, CancellationToken, Task>> allHandlers, INotification notification, CancellationToken cancellationToken)
        {
            foreach (var handler in allHandlers)
            {
                await handler(notification, cancellationToken).ConfigureAwait(false);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="notification"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private Task PublishNotification(INotification notification, CancellationToken cancellationToken = default)
        {
            var notificationType = notification.GetType();
            var handler = _notificationHandlers.GetOrAdd(notificationType,
                t => (NotificationHandlerWrapper)Activator.CreateInstance(typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType)));

            return handler.Handle(notification, cancellationToken, _serviceFactory, PublishCore);
        }
    }

这里面有几个知识点需要学习:

  1. 作者使用了ConcurrentDictionary<TKey, TValue> 类型来承载处理程序类型。这是一个线程安全的集合类型(专业一点的话:多线程同步字典集合),其命名空间是System,Collections.Concurrent,是在.net4.0以后推出的,它的非安全类型Dictionary<TKey, TValue> 想必很多人都不陌生。
  2. 源码中的另一个私有字段:ServiceFactory _serviceFactory 。 可以看出MediatR的构造函数是聚合了ServiceFactory这个类型的。注释翻译:用于解析所有服务的工厂方法。对于多个实例,它将解析为IEnumerable<T>。这是作者在IOC(控制反转)以及DI(依赖注入)方面的一些实现。它是一个工厂,通过IOC和DI,当你使用的时候,指定一个类型,他将在服务容器里面直接给你解析出一个或者多个实例出来。
  3. MediatR支持发送两种消息。
    • request/response 式消息,分派到一个处理程序,由一个处理程序处理。
    • Notification messages通知式消息,分派到多个处理程序,由多个处理程序处理。
    这也是为什么IMediator接口和Mediator类里面的方法只有两种的原因:Send()Publish() 。前者是一对一的请求/响应式;后者是一对多的通知式。
  4. 继续深挖源码,作者定义了两个接口类型,分别两个类文件:IRequestINotification 。这两个接口没有任何的成员,在其他类型实现这两个接口的时候,它们只是起到约束的作用。因为作者在源码中大量使用了泛型类型以及泛型方法,熟悉泛型的程序猿都知道:泛型约束这么个概念。
    在我们使用这个类库的时候,当我们定义的类型实现了IRequest接口的时候,那就是第一种处理消息的方式(request/response),它还分带响应和不带响应两种;当我们定义的类型实现了INotification接口的时候,那就是第二种处理消息的方式(notification messages)。
    /// <summary>
    /// Marker interface to represent a request with a void response
    /// 标记接口,代表一个无需返回响应的请求
    /// </summary>
    public interface IRequest : IRequest<Unit> { }

    /// <summary>
    /// Marker interface to represent a request with a response
    /// 标记接口,代表一个有响应类型的请求
    /// </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 { }
    /// <summary>
    /// Marker interface to represent a notification
    /// (标记接口,代表一个通知)
    /// </summary>
    public interface INotification { }
  1. 继续深挖Mediator这个类型。你会发现在Send()Publish() 方法中分别有一个RequestHandlerWrapperImpl<,>NotificationHandlerWrapperImpl<>,还有RequestHandlerWrapper<TResponse>NotificationHandlerWrapper。这些是作者在项目中定义的一些内部类型,里面有一些C#的高级语法,比如:局部函数、表达式形式的成员函数、Linq高阶函数Aggregate、构造委托函数链,不了解的可以去了解了解。话说作者源码写得很精简,但不宜让人理解,所以有些地方我给重构了一下,方便自己能看懂。
internal abstract class RequestHandlerBase
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request">具体同事类</param>
        /// <param name="cancellationToken"></param>
        /// <param name="serviceFactory">服务工厂</param>
        /// <returns></returns>
        public abstract Task<object?> Handle(object request, CancellationToken cancellationToken, ServiceFactory serviceFactory);

        /// <summary>
        /// 获取处理程序
        /// </summary>
        /// <typeparam name="THandler"></typeparam>
        /// <param name="factory">服务工厂</param>
        /// <returns></returns>
        protected static THandler GetHandler<THandler>(ServiceFactory factory)
        {
            THandler handler;

            try
            {
                handler = factory.GetInstance<THandler>();
            }
            catch (Exception e)
            {
                throw new InvalidOperationException($"Error constructing handler for request of type {typeof(THandler)}. Register your handlers with the container. See the samples in GitHub for examples.", e);
            }

            if (handler == null)
            {
                throw new InvalidOperationException($"Handler was not found for request of type {typeof(THandler)}. Register your handlers with the container. See the samples in GitHub for examples.");
            }

            return handler;
        }
    }


    internal abstract class RequestHandlerWrapper<TResponse> : RequestHandlerBase
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="request"></param>
        /// <param name="cancellationToken"></param>
        /// <param name="serviceFactory"></param>
        /// <returns></returns>
        public abstract Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken, ServiceFactory serviceFactory);
    }


    /// <summary>
    /// 请求处理器程序包装器的实现。
    /// </summary>
    /// <typeparam name="TRequest"></typeparam>
    /// <typeparam name="TResponse"></typeparam>
    internal class RequestHandlerWrapperImpl<TRequest, TResponse> : RequestHandlerWrapper<TResponse> where TRequest : IRequest<TResponse>
    {
        /// <summary>
        /// 重写抽象类的Handle方法
        /// </summary>
        /// <param name="request">具体同事类</param>
        /// <param name="cancellationToken"></param>
        /// <param name="serviceFactory">服务工厂</param>
        /// <returns></returns>
        public override Task<object?> Handle(object request, CancellationToken cancellationToken, ServiceFactory serviceFactory)
        {
            return Handle((IRequest<TResponse>)request, cancellationToken, serviceFactory)
                .ContinueWith(t =>
                {
                    if (t.IsFaulted)
                    {
                        ExceptionDispatchInfo.Capture(t.Exception.InnerException).Throw();
                    }
                    return (object?)t.Result;
                }, cancellationToken);
        }

        /// <summary>
        /// 重写抽象类的Handle方法
        /// </summary>
        /// <param name="request">具体同事类</param>
        /// <param name="cancellationToken"></param>
        /// <param name="serviceFactory">服务工厂</param>
        /// <returns></returns>
        public override Task<TResponse> Handle(IRequest<TResponse> request, CancellationToken cancellationToken, ServiceFactory serviceFactory)
        {
            //此处是局部函数&&表达式形式的成员函数(C#7.0新语法)
            //这一步的目的,是将一个类型函数对象转换为局部函数,方便后续调用。
            //相当于下面的写法
            //Task<TResponse> Handler() => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);

            Task<TResponse> Handler()
            {
                return GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory)//从IOC容器获取该请求对应的请求处理器
                    .Handle((TRequest) request, cancellationToken);//调用Handle方法
            }

            //将函数调用转换为委托
            RequestHandlerDelegate<TResponse> handler = () => GetHandler<IRequestHandler<TRequest, TResponse>>(serviceFactory).Handle((TRequest) request, cancellationToken);

            var instances = serviceFactory.GetInstances<IPipelineBehavior<TRequest, TResponse>>();//从IOC容器获取所有的IPipelineBehavior的实现,用于构造请求管道
            var func = instances.Reverse()//集合反转
                                .Aggregate((RequestHandlerDelegate<TResponse>) Handler,//将局部函数强转为委托,等价于handler
                                            (next, pipeline) => () => pipeline.Handle((TRequest) request, cancellationToken, next)//借助委托构造函数链,等价于combineFunc
                                            );

            Func<RequestHandlerDelegate<TResponse>, IPipelineBehavior<TRequest, TResponse>, RequestHandlerDelegate<TResponse>> combineFunc = (delegater, behavior) =>
            {
                //将类型函数对象转换为委托
                RequestHandlerDelegate<TResponse> combineDelegate = () => behavior.Handle((TRequest) request, cancellationToken, delegater);

                //上面的写法相当于这个写法
                //RequestHandlerDelegate<TResponse> combineDelegate = delegate { return behavior.Handle((TRequest)request, cancellationToken, delegater};

                return combineDelegate;
            };
            return func();//进行委托调用并返回  
        }
    }

6.最后一点东西:处理管道Pipeline。这里只讲一下原理和概念,不贴源码了。其实就像http请求管道,我们使用.net core在Startup类的Configure()方法中添加的中间件一样,是有一定逻辑顺序在里面的。它这个也是有顺序的,就是请求处理之前做什么、请求处理之后做什么。其实这也是用到了一个设计模式的思想。源码里有这么几个接口:IPipelineBehaviorIRequestPreProcessorIRequestPostProcessorIRequestExceptionActionIRequestExceptionHandler

MeidatR支持按需配置请求管道进行消息处理。即支持在请求处理前和请求处理后添加额外行为。仅需实现IRequestPreProcessorIRequestPostProcessor两个接口,并将前三个服务接口注册到Ioc容器即可。

services.AddScoped(typeof(IPipelineBehavior<,>), typeof(GenericPipelineBehavior<,>));//添加通用管道行为服务
services.AddScoped(typeof(IRequestPreProcessor<>), typeof(GenericRequestPreProcessor<>));//添加通用请求前预处理程序服务
services.AddScoped(typeof(IRequestPostProcessor<,>), typeof(GenericRequestPostProcessor<,>));//添加通用请求后处理程序服务

总体上就以上这些知识点了,基本上快将源码囊括完了,只剩下个别类型没有说明,这些对于使用不是很重用吧。。。 我们的作者提供了一个专为方便.net core依赖注入的类库项目,里面源码不多,会用即可。最后附上地址:MediatR.Extensions.Microsoft.DependencyInjection

MediatR能做什么,以及使用场景

进程内消息的发送和处理过程之间解耦。 根据中介者模式的思想,去理解这个项目。 这应该就是它的使用场景和作用。

喝个汤

最近辞了工作,做起全职爸爸了,娃娃现在10个多月了。之前是我妈在身边带孩子,现在我妈回老家了(在老家待到国庆节以后了),正好我前面工作干的也不爽,索性辞职当起全职爸爸了,然后老婆一个人上班。 说实话,带娃挺累的。他需要有人一直陪着,不然他爬来爬去,双手到处乱抓,很危险和不卫生。一会儿大人没在身边就哇哇地哭闹。。。只能等他玩累了睡着或者晚上的时候抽空学习学习。
最后附上俺娃,近期的照片一张。