• 使用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,我以前只是听说的,没有认真研究过。上周末的胶东开发者技术沙龙中,衣哥也提到了这个库,闲暇时间我就研究了一下,并修改了之前的例子,发现确实简化了不少代码。

    如果没有看过之前的文章,建议你先看一下之前的实现,本文中的所有修改都是针对上一篇的代码。

    中介者模式

    中介者模式,定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。

    中介者模式是一种对象行为型模式,其主要优点如下。

    1. 降低了对象之间的耦合性,使得对象易于独立地被复用。
    2. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

    其实事件发布/订阅就是中介者模式的一种实现方式。

    什么是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的中介者模式实现,它虽然只支持进程内的消息传递,但是却可以简化事件发布/订阅代码,帮助实现业务逻辑代码的解耦,你可以自己试一试。

  • 相关阅读:
    MySQL 性能监控4大指标——第二部分
    mysql 复制A表 到B表;insert into select * from table
    spring boot整合mybatis查询数据库返回Map字段为空不返回解决
    sprinbboot 热部署 造成类加载器 不一致问题
    springboot 整合dubbo 消费模块引入springboot 之后自动注入jdbc 模块导致启动报错问题
    基于Cent os 云服务器中SVN 服务器的搭建---具体实践是可行的 一次备注便于后续查找
    centos7 yum安装配置redis 并设置密码
    ccenteros 部署 redis
    linux ccenteros 部署 redis
    mybatis 插入日期类型精确到秒的有关问题
  • 原文地址:https://www.cnblogs.com/lwqlun/p/10640280.html
Copyright © 2020-2023  润新知