使用MediatR重构单体应用中的事件发布/订阅
标题:使用MediatR重构单体应用中的事件发布/订阅
作者:Lamond Lu
地址:https://www.cnblogs.com/lwqlun/p/10640280.html
源代码:https://github.com/lamondlu/EventHandlerInSingleApplication

背景
在之前的一篇文章中,我分享了一个在ASP.NET Core单体程序中,使用事件发布/订阅解耦业务逻辑的例子。

项目源代码地址:https://github.com/lamondlu/EventHandlerInSingleApplication
在文章评论中老张提到了使用MediatR的方案。对于MediatR,我以前只是听说的,没有认真研究过。上周末的胶东开发者技术沙龙中,衣哥也提到了这个库,闲暇时间我就研究了一下,并修改了之前的例子,发现确实简化了不少代码。
如果没有看过之前的文章,建议你先看一下之前的实现,本文中的所有修改都是针对上一篇的代码。
中介者模式
中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。
中介者模式是一种对象行为型模式,其主要优点如下。
- 降低了对象之间的耦合性,使得对象易于独立地被复用。
- 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
其实事件发布/订阅就是中介者模式的一种实现方式。
什么是MediatR
MediatR是一个基于.NET的中介者模式实现库,它是一种进程内消息传递的方案,官网地址https://github.com/jbogard/MediatR/。
MediatR可以发送两种消息
- 请求/响应消息,这种消息只有一个处理程序, 这种方式的消息需要实现
IRequest接口, 其处理程序需要实现IRequestHandler接口 - 通知消息,这种消息可以有一个或多个处理程序,这种方式的消息需要实现
INotification接口, 其处理程序需要实现INotificationHandler接口
从消息的特性上看,如果要改造我们之前的事件发布/订阅功能,我们需要使用通知消息,因为每个事件可能会有一个或多个的处理程序。
添加MediatR
在.NET Core中可以直接使用Nuget添加MediatR.Extensions.Microsoft.DependencyInjection库来引入MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
添加完成后,我们还需要在Startup.cs中启动MediatR中间件。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
...
services.AddMediatR();
}
现在我们就可以在项目中使用MediatR了。
提示:
这里你可以会有疑问,之前的代码中,我们这里还定义了事件和处理器之间的映射,现在怎么就不需要了?
EventHandlerContainer
.Subscribe<ShoppingCartSubmittedEvent, CreateOrderHandler>();
EventHandlerContainer
.Subscribe<ShoppingCartSubmittedEvent, ConfirmEmailSentHandler>();
这里MediatR中已经提供了一个自动映射功能,它会在程序启动时,自动搜索所有事件和事件处理器,并自动设置好它们之间的映射,所以我们就不需要在手动做这个事情了。
创建Notification
在我们之前的代码中,我们定义了一个购物车提交事件,它继承自事件基类EventBase。
public class ShoppingCartSubmittedEvent : EventBase
{
public ShoppingCartSubmittedEvent()
{
Items = new List<ShoppingCartSubmittedItem>();
}
public List<ShoppingCartSubmittedItem> Items { get; set; }
}
现在改用MediatR之后,我们需要修改当前事件的定义,让它实现INotification接口。
public class ShoppingCartSubmittedEvent : INotification
{
public ShoppingCartSubmittedEvent()
{
Items = new List<ShoppingCartSubmittedItem>();
}
public List<ShoppingCartSubmittedItem> Items { get; set; }
}
NotificationHandler
完成事件定义部分的修改之后,我们还需要重构事件处理器的代码。
在之前的代码中,针对购物车提交事件,我们定义了两个处理器,一个是创建订单处理器CreateOrderHandler,一个是发送邮件处理器ConfirmEmailSentHandler。
现在我们来使用INotificationHandler接口来改造之前定义好的两个处理器。
public class CreateOrderHandler : INotificationHandler<ShoppingCartSubmittedEvent>
{
private IOrderManager _orderManager = null;
public CreateOrderHandler(IOrderManager orderManager)
{
_orderManager = orderManager;
}
public Task Handle(ShoppingCartSubmittedEvent notification, CancellationToken cancellationToken)
{
_orderManager.CreateNewOrder(new Models.DTOs.CreateOrderDTO
{
Items = notification.Items.Select(p => new Models.DTOs.NewOrderItemDTO
{
ItemId = p.ItemId,
Name = p.Name,
Price = p.Price
}).ToList()
});
return Task.CompletedTask;
}
}
public class ConfirmEmailSentHandler : INotificationHandler<ShoppingCartSubmittedEvent>
{
public Task Handle(ShoppingCartSubmittedEvent notification, CancellationToken cancellationToken)
{
Console.WriteLine("Confirm Email Sent.");
return Task.CompletedTask;
}
}
代码解释:
INotificationHandler是一个泛型接口,接口中定义的泛型类需要实现INotification接口- 当处理器实现
INotificationHandler接口时,就需要实现一个Handle方法, 在该方法中,我们可以编写具体的业务代码- 从方法的返回值Task, 你可以了解到这个方法是没有返回值的,并且可以使用async/await变为一个异步的版本。
发布事件
在之前的代码中,当购物车提交成功之后,我们会在OrderManager类中,使用EventContainer发布事件。当我们使用MediatR之后,这部分代码稍有改动, 我们需要使用IMediator接口对象的Publish方法来发布事件。
public void SubmitShoppingCart(string shoppingCartId)
{
var shoppingCart = _unitOfWork.ShoppingCartRepository.GetShoppingCart(shoppingCartId);
_unitOfWork.ShoppingCartRepository.SubmitShoppingCart(shoppingCartId);
_mediator.Publish(new ShoppingCartSubmittedEvent()
{
Items = shoppingCart.Items.Select(p => new ShoppingCartSubmittedItem
{
ItemId = p.ItemId,
Name = p.Name,
Price = p.Price
}).ToList()
});
_unitOfWork.Save();
}
最终效果
至此,所有代码就都完成了,我们可以按照上一篇的操作步骤,再测试一次。

当执行购物车提交操作的时候,订单创建和邮件发送处理器都正确触发了。
总结
MediatR是一个基于.NET的中介者模式实现,它虽然只支持进程内的消息传递,但是却可以简化事件发布/订阅代码,帮助实现业务逻辑代码的解耦,你可以自己试一试。
使用MediatR重构单体应用中的事件发布/订阅的更多相关文章
- ASP.NET Core中实现单体程序的事件发布/订阅
标题:ASP.NET Core中实现单体程序的事件发布/订阅 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/10468058.html 项目源代码: ...
- guava的事件发布订阅功能
事件的重要性,不用说很重要,在很多时候我们做完一个操作的时候,需要告知另外一个对象让他执行相应操作,比如当用户注册成功的时候,需要抛出一个注册成功的事件,那么有监听器捕获到这个事件,完成后续用户信息初 ...
- 模块(类)之间解耦利器:EventPublishSubscribeUtils 事件发布订阅工具类
如果熟悉C#语言的小伙伴们一般都会知道委托.事件的好处,只需在某个类中提前定义好公开的委托或事件(委托的特殊表现形式)变量,然后在其它类中就可以很随意的订阅该委托或事件,当委托或事件被触发执行时,会自 ...
- Blazor+Dapr+K8s微服务之事件发布订阅
我们要实现的是:在blazorweb服务中发布一个事件,并传递事件参数,然后在serviceapi1服务中订阅该事件,接收到blazorweb服务中发布的事件和参数. 1 在blazo ...
- AKKA集群中的分布式发布订阅
集群中的分布式发布订阅 如何向一个不知道在哪个节点上运行的actor发送消息呢? 如何向集群中的所有actor发送感兴趣的主题的消息? 这种模式提供了一个中介actor,akka.cluster.pu ...
- spring中的事件发布与监听
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. spring事件发布与监听的应用场景 当处理完一段代码逻辑,接下来需要同 ...
- JS中什么是发布--订阅模式?
转载文章部分内容: 发布订阅模式介绍 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. ...
- spring#事件发布订阅
1. 如果在应用中发生了某些事件,事件会被拦截和处理就好了,这样就有了很大的灵活性,至少代码不会紧密的耦合在一起, 代码的解耦就是业务的解耦,业务A的代码不用手动的调用业务B的代码,业务B只需要监听相 ...
- python中使用redis发布订阅者模型
redis发布订阅者模型: Redis提供了发布订阅功能,可以用于消息的传输,Redis的发布订阅机制包括三个部分,发布者,订阅者和Channel.发布者和订阅者都是Redis客户端,Channel则 ...
随机推荐
- C++判断字符串是否为空的一个小问题
刚才visual studio下处理一个函数字符串入参,判断入参字符串是否为空有点小问题. 接口函数声明是这样的:SHORT GETWFSINFPINKEYDETAIL(LPCTSTR strKeyN ...
- java I/O (一)
java 的I/O类基本上可以分为6大类:二进制的输入,二进制的输出,文本的输入,文本的输出,FILTER类和其他对文件操作的工具类. Java的I/O类主要在java.io的包里,其中两个主要的流为 ...
- 2018山东省ACM省赛G题-Game
Alice and Bob are playing a stone game. There are n piles of stones. In each turn, a player can remo ...
- 洛谷 P1613 解题报告
P1613 跑路 题目描述 小\(A\)的工作不仅繁琐,更有苛刻的规定,要求小\(A\)每天早上在\(6:00\)之前到达公司,否则这个月工资清零.可是小\(A\)偏偏又有赖床的坏毛病.于是为了保住自 ...
- 团队项目第二阶段个人进展——Day4
一.昨天工作总结 冲刺第四天,分析完成后端处理的数据有主题,时间,地点,照片信息,前几个都是字符串类型,后一个是照片格式 二.遇到的问题 照片格式数据不知道怎么处理 三.今日工作规划 学习后端小程序后 ...
- cropper(裁剪图片)插件使用(案例)
公司发布微信H5前端阵子刚刚弄好的H5端的图片上传插件,现在有需要裁剪图片.前端找了一个插件---cropper 本人对这插件不怎么熟悉,这个案例最好用在一个页面只有一个上传图片的功能上而且只适合单个 ...
- @ConditionalOnMissingBean注解理解
@ConditionalOnMissingBean注解作用在@bean定义上,它的作用就是在容器加载它作用的bean时,检查容器中是否存在目标类型(ConditionalOnMissingBean注解 ...
- C# Linq GroupBy 分组过滤求和
var delOrderData = orderLogList.Where(x => (x.OlStatus == 0 && x.OlUpId == null)).GroupBy ...
- Kali Linux虚拟机安装完整安装过程及简单配置(视频)
点击播放视频 附:视频中出现的两个txt文本,包含了大致的安装与配置过程: 文本1:KaliLinux虚拟机安装和初步配置 Kali Linux虚拟机安装和初步配置 大家好,今天给大家演示一下在VMw ...
- HTML学习之制作导航网页
前言 今天用HTML写了一个网址导航,源代码如下: <html> <head> <title>网址导航</title> </head> &l ...