34:MediatR:轻松实现命令查询职责分离模式(CQRS)(上)-开发实战
.NET Core开发实战前文传送门:
- 第1课:课程介绍
- 第2课:内容综述
- 第3课:.NET Core的现状、未来以及环境搭建
- 第4课:Startup:掌握ASP.NET Core的启动过程
- 第5课:依赖注入:良好架构的起点(上)
- 第5课:依赖注入:良好架构的起点(中)
- 第5课:依赖注入:良好架构的起点(下)
- 第6课:作用域与对象释放行为(上)
- 第6课:作用域与对象释放行为(下)
- 第7课:用Autofac增强容器能力(上)
- 第7课:用Autofac增强容器能力(下)
- 第8课:配置框架:让服务无缝适应各种环境
- 第9课:命令行配置提供程序
- 第10课:环境变量配置提供程序
- 第11课:文件配置提供程序
- 第12课:配置变更监听
- 第13课:配置绑定:使用强类型对象承载配置数据
- 第14课:自定义配置数据源:低成本实现定制化配置方案
- 第15课:选项框架:服务组件集成配置的最佳实践
- 第16课:选项数据热更新:让服务感知配置的变化
- 第17课:为选项数据添加验证:避免错误配置的应用接收用户流量
- 第18课:日志框架:聊聊记日志的最佳姿势(上)
- 第18课:日志框架:聊聊记日志的最佳姿势(下)
- 第19课:日志作用域:解决不同请求之间的日志干扰
- 第20课:结构化日志组件Serilog:记录对查询分析友好的日志
- 第21课:中间件:掌控请求处理过程的关键(上)
- 第21课:中间件:掌控请求处理过程的关键(下)
- 第22课:异常处理中间件:区分真异常与逻辑异常(上)
- 第22课:异常处理中间件:区分真异常与逻辑异常(下)
- 第23课:静态文件中间件:前后端分离开发合并部署骚操作(上)
- 第23课:静态文件中间件:前后端分离开发合并部署骚操作(下)
- 第24课:文件提供程序:让你可以将文件放在任何地方
- 第25课:路由与终结点:如何规划好你的Web API(上)
- 第25课:路由与终结点:如何规划好你的Web API(下)
- 第26课:工程结构概览:定义应用分层及依赖关系
- 第27课:定义Entity:区分领域模型的内在逻辑和外在行为
- 第28课:工作单元模式(UnitOfWork):管理好你的事务
- 第29课:定义仓储:使用EF Core实现仓储层
- 第30课:领域事件:提升业务内聚,实现模块解耦
- 第31课:APIController:定义API的最佳实践
- 第32课:集成事件:解决跨微服务的最终一致性
- 第33课:集成事件:使用RabbitMQ来实现EventBus(上)
- 第33课:集成事件:使用RabbitMQ来实现EventBus(下)
34 | MediatR:轻松实现命令查询职责分离模式(CQRS)
核心对象
- IMeditator
- IRequese、IRequest
- IRequestHandler<in TRequest, TResponse>
源码链接:
https://github.com/witskeeper/geektime/tree/master/samples/MediatorDemo
首先我们安装了 MediatR 的 8.0 的组件包,还安装了依赖注入框架的扩展包,以及依赖注入框架的核心组件包
- MediatR
- MediatR.Extensions.Microsoft.DependencyInjection
- Microsoft.Extensions.DependencyInjection
大家可以观察到 MediatR 的包名和命名空间少了一个 o,猜测是作者故意这样设计的,因为它具体实现里面会有一个接口和类是 Mediator,如果设置同名的话会有一些引用上的问题
var services = new ServiceCollection(); services.AddMediatR(typeof(Program).Assembly);
我们在这里构建一个 ServiceCollection,然后通过一行代码将我们当前的程序集注入进去,它就可以扫描我们当前程序集相关的类,下面看一下我们定义的两个类
internal class MyCommand : IRequest<long> { public string CommandName { get; set; } } internal class MyCommandHandler : IRequestHandler<MyCommand, long> { public Task<long> Handle(MyCommand request, CancellationToken cancellationToken) { Console.WriteLine($"MyCommandHandler执行命令:{request.CommandName}"); return Task.FromResult(10L); } }
第一个类是 MyCommand,它实现了 IRequest 接口,这个接口就代表中介者要执行的命令
第二个类是 MyCommandHandler,它实现了 IRequestHandler 的接口,这个就是我们对命令的处理器的定义
var serviceProvider = services.BuildServiceProvider(); var mediator = serviceProvider.GetService<IMediator>(); await mediator.Send(new MyCommand { CommandName = "cmd01" });
我们从容器里面获取一个 IMediator,然后通过 send 方法发送一个 MyCommand 命令,我们构造了一个新的 MyCommand 的实例传给它
启动程序,输出如下:
MyCommandHandler执行命令:cmd01
我们可以看到 MyCommandHandler 的 Handle 方法执行了,它输出了 MyCommandHandler 的执行命令 cmd01
这样子,这个中介者它有什么好处呢?
大家可以看到,通过中介者模式,我们将命令的构造和命令的处理可以分离开,那么命令的处理如何知道要处理哪个命令呢,就是通过我们泛型的约束来定义的,我们这里为 IRequestHandler 填入了 MyCommand 类型,所以我们能明确知道 MyCommandHandler 是用来处理 MyCommand 的
如果说我在程序里面实现了多个 Handler,我们可以试验一下
internal class MyEventHandlerV2 : INotificationHandler<MyEvent> { public Task Handle(MyEvent notification, CancellationToken cancellationToken) { Console.WriteLine($"MyEventHandlerV2执行:{notification.EventName}"); return Task.CompletedTask; } }
启动程序,输出如下:
MyCommandHandlerV2执行命令:cmd01
大家可以看到我们输出的是 V2 执行命令
我们把代码进行一个调整,把这个定义移到后面
internal class MyEventHandler : INotificationHandler<MyEvent> { public Task Handle(MyEvent notification, CancellationToken cancellationToken) { Console.WriteLine($"MyEventHandler执行:{notification.EventName}"); return Task.CompletedTask; } } internal class MyEventHandlerV2 : INotificationHandler<MyEvent> { public Task Handle(MyEvent notification, CancellationToken cancellationToken) { Console.WriteLine($"MyEventHandlerV2执行:{notification.EventName}"); return Task.CompletedTask; } }
启动程序,输出如下:
MyCommandHandler执行命令:cmd01
大家可以看到我们这次输出的并不是 V2,而是之前的那个命令,为什么会这样子呢?是因为实际上 mediator 对于 IRequestHandler 的扫描,它是有顺序的,后面扫描到的会替换前面扫描到的 Handler,它只会识别其中最后注册进去的一个,也就是说我们在处理 RequestHandler 的时候,我们要注意在注册时仅注册需要的那个
我们再来看看我们的应用程序,回到我们之前的工程里
namespace GeekTime.API.Application.Commands { public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long> { IOrderRepository _orderRepository; ICapPublisher _capPublisher; public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher) { _orderRepository = orderRepository; _capPublisher = capPublisher; } public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken) { var address = new Address("wen san lu", "hangzhou", "310000"); var order = new Order("xiaohong1999", "xiaohong", 25, address); _orderRepository.Add(order); await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken); return order.Id; } } }
我们可以看到我们的 CreateOrderCommandHandler 实现的是 IRequestHandler,这也就是解释了为什么之前我们并没有显示的调用 CreateOrderCommandHandler,代码却能够执行到这里的原因

原文出处:微信公众号【 郑子铭 DotNet NB】
原文链接:https://mp.weixin.qq.com/s/RRoHtXI95GOB0vPdotUktQ
本文观点不代表Dotnet9立场,转载请联系原作者。