中介者模式实现与MediatR应用

概要

1. Console App Demo
2. Web API + MediatR Demo

Console App Demo

简单的Console App Demo,包括使用自建的中介者对象实现以及MediatR实现

Web API + MediatR Demo

在WebAPI中使用MediatR,最大的改变是将原本注入到controller的依赖分散到了MediatR消息的处理方法中

传统的依赖注入如下图
传统的依赖注入

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

而接口则变得非常简洁,即
1.发送MediatR消息
2.获得结果
3.返回结果

在MediatR消息的处理方法中,当然也可以通过注入MediatR来再次发送消息,即嵌套的调用。最大程度得分离逻辑来降低逻辑上的耦合

...
using MediatR;
using Application.Exceptions;
using Application.Item.Models;
using Data.RepositoryInterfaces;
using Domain.BaseInterfaces;
using Domain.Entities;

namespace WebApp.Controllers
{

    [ApiController]
    public class ItemController : ControllerBase
    {

        private readonly IMediator _mediator;

        public ItemController(IMediator mediator)
        {
            _mediator = mediator;
        }

        [HttpGet]
        [Route("")]
        public async Task<ActionResult> Get(int itemId)
        {
            var result = await _mediator.Send(new GetItem(id, userid));
            if (result == null) return NotFound();
            return Ok(result);
        }
    }
}

namespace Application.Item.Queries
{
    public class GetItem : IRequest<ItemViewModel>
    {
        public GetItem(int itemId, string userId)
        {
            UserId = userId;
            ItemId = itemId;
        }

        public string UserId { get; }
        public int ItemId { get; }
    }

    public class GetItemHandler : IRequestHandler<GetItem, ItemViewModel>
    {
        private readonly IMediator _mediator;
        private readonly IItemRepository _itemRepository;

        public GetItemHandler(
            IMediator mediator,
            IItemRepository itemRepository
        )
        {
            _mediator = mediator;
            _itemRepository = itemRepository;
        }

        public async Task<ItemViewModel> Handle(GetItem arg, CancellationToken cancellationToken)
        {
            var item = await _itemRepository.Get()
                .Where(i => i.Id == arg.ItemId)
                .Select(i => new
                {
                    Id = i.Id,
                    Name = i.Name,
                }).SingleOrDefaultAsync();

            if (item == null)
                throw new NotFoundException(arg.ItemId.ToString(), nameof(Domain.Entities.Item));

            var companyUser = await _mediator.Send(new GetCompanyUser(arg.UserId, item.CompanyId));

            bool isCompanyUser = companyUser != null;

            return new ItemViewModel(){
                ItemId = item.Id,
                ItemName = item.Name,
                IsCompanyUser = isCompanyUser
            };
        }
    }

    public class GetItemPreProcessor : IRequestPreProcessor<GetItem>
    {
        private readonly IMediator _mediator;
        public GetItemPreProcessor(
            IMediator mediator)
        {
            _mediator = mediator;
        }

        public Task<ItemViewModel>  Process(GetItem request, CancellationToken cancellationToken)
        {
            return await _mediator.Send(new InsertItemViewRecord(request));
        }
    }
}


namespace Application.Item.Commands
{
    //implement InsertItemViewRecord
}

Demo主要流程如下图

中介者设计模式与MediatR

概要

1. 中介者模式
2. MediatR

中介者模式

什么是中介者模式

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

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

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

为什么要使用中介者模式

1.程序随功能增多产生复杂的依赖关系,某一方法可能分别依赖不同层级的对象

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

3.由于类间的相互引用而产生高耦合的代码

中介者模式如何解决问题

以WebApi工程为例,最上层的controller只依赖于中介者对象,中介者对象依赖于业务逻辑。

1.根据中介者的位置进行分割,此时中介者作为controller和业务逻辑之间的分隔起到解耦的作用。上层的controller与下层的业务逻辑始终只与中介者交互,两部分即消息的发送和接收者可以独立的修改,controller的替换或者更新的工作量也就大大减少。

2.在我们对所有的逻辑都尽可能地进行分割的前提下,通过于中介者交互获得某一逻辑片段的响应,组合后完成复杂逻辑。

原本的复杂逻辑可能依赖于多个下层方法,分隔后复杂逻辑只依赖于中介者。

原本依赖的多个下层方法现在分布在各个逻辑片段中,由此,下层方法的改动只对这些逻辑片段产生影响。

3.以复杂逻辑为例,中介消息的接收者只表明调用各个逻辑的顺序以及组合方式。逻辑片段及其依赖的下层方法的修改只要保证自身的返回内容正确,符合请求目的,就不会对其他的所依赖的对象产生影响

中介者模式存在的问题

有的朋友可能说了,我只要写一个巨大的类,把所有的业务逻辑的方法都放进去,让所有的controller都依赖于这个类,那我是不是也实现了中介者模式?

从结构上来说,这个类也是起到了中介者的角色。但是随着业务增加,中介者对象变得臃肿庞大。

其次这个巨大的类相当于对所有的下层方法产生了依赖,每次使用这个类时,都需要将所有的依赖注入,与依赖注入的目的相违背。

而MediatR在一定程度上解决了这个问题。MediatR提供了简洁且功能丰富的中介者对象
并将依赖向外部分散,依赖注入到细致的下层逻辑中

MediatR

什么是MediatR

.NET中的中介者模式实现,一种进程内消息传递框架,提供了中介者对象
MediatR支持以同步或异步的形式进行请求/响应,支持消息的单播/多播

WebApp中应用MediatR

MediatR在WebApi中的位置处于Application与Presentation/Controller之间,请求进入后发送通过MediatR消息,消息处理方法中调用Application

由此,MediatR作为Presentation/Controller和Application的中间连接件,前者只对MediatR产生依赖,而MediatR只对后者产生依赖

把MediatR inject 到 presentation和把service inject到presentation有什么区别和各自的利弊?

首先就是两个依赖关系不一样 理想状态下是presentation只对mediator中介者产生依赖 如果是service直接注入的话 通常情况下不可能只注入一个 presentation就会产生多个引用 增加两者之间的耦合

MediatR把presentation和service分隔开来 这样两个部分的修改变更就更简单

优势是MediatR能分散所有的依赖 降低耦合
弊端是中介者成了程序不能分割的一部分 所以他必须要可靠高效

直接调用与通过MediatR调用比较
功能 通过MediaR调用
调用方法的前后进行额外处理 通过IPipelineBehavior实现类似中间件的功能,通过IRequestPreProcess,IRequestpostProcess来实现为某一方法调用前后附上额外运行的代码
调用方法触发运行一个或多个代码段 通过INotification实现消息的单播/多播
捕捉异常 通过IRequestExceptionHandler,IRequestExceptionAction处理执行方法中产生的异常,运行额外代码
Notification消息的发布策略

通过重写MediatR.PublishCore方法来实现自定义的发布策略
默认发布策略是遍历所有消息的handlers, 逐一执行