事件发布/订阅是一种非常强大的模式,它可以帮助业务组件间实现完全解耦,不同的业务组件只依赖事件,只关注哪些事件是需要自己处理的,而不用关注谁来处理自己发布事件,事件追溯(Event Sourcing)也是基于事件发布/订阅的。在微服务架构中,事件发布/订阅有非常多的应用场景。今天我给大家分享一个基于ASP.NET Core的单体程序使用事件发布/订阅的例子,针对分布式项目的事件发布/订阅比较复杂,难点是事务处理,后续我会另写一篇博文来演示。
案例说明
当前我们有一个基于ASP.NET Core的电子商务系统,在项目的初期,业务非常简单,只有一个购物车模块和一个订单模块,所有的代码都放在一个项目中。
整个项目使用了一个简单的三层架构。
center">
这里当用户提交购物车的时候,程序会在ShoppingCartManager类的SubmitShoppingCart方法中执行3个操作
修改当前购物车的状态为完成
根据购物车中的物品创建一个新订单
给用户发邮件
代码如下:
public void SubmitShoppingCart(string shoppingCartId) { var shoppingCart = _unitOfWork.ShoppingCartRepository .GetShoppingCart(shoppingCartId); _unitOfWork.ShoppingCartRepository .SubmitShoppingCart(shoppingCartId); _unitOfWork.OrderRepository .CreatOrder(new CreateOrderDTO { Items = shoppingCart.Items .Select(p => new NewOrderItemDTO { ItemId = p.ItemId, Name = p.Name, Price = p.Price }).ToList() }); //这里为了简化代码,我用命令行表示发送邮件的逻辑 Console.WriteLine("Confirm Email Sent."); _unitOfWork.Save(); }
根据SOLID设计原则中的单一责任原则,如果一个类承担的职责过多,就等于把这些职责耦合在一起了。这里生成订单和发送邮件都不应该是当前SubmitShoppingCart需要负责的,所以我们需要它们从这个方法中移出去,使用的方法就是事件订阅/发布。
新的架构图
以下是使用事件发布/订阅之后的系统架构图。
这里我们会创建一个购物车提交事件ShoppingCartSubmittedEvent。
当站点启动的时候,我们会在一个名为EventHandlerContainer的类中注册订阅ShoppingCartSubmittedEvent事件的2个处理类CreateOrderHandler和ConfirmEmailSentHandler。
在SubmitShoppingCart方法中,我们会做2件事情:
更改当前购物车的状态。
发布ShoppingCartSubmittedEvent事件。
CreateOrderHandler事件处理器会调用OrderManager类中的创建订单方法。
ConfirmEmailSentHandler事件处理器会负责发送邮件。
好的,下面让我们来一步一步实现以上描述的代码。
添加事件基类
这里我们首先定义一个事件基类,其中暂时只添加了一个属性OccuredOn,它表示了当前事件的触发时间。
public class EventBase { public EventBase() { OccuredOn = DateTime.Now; } protected DateTime OccuredOn { get; set; } }
定义购物车提交事件
接下来我们就需要创建购物车提交事件类ShoppingCartSubmittedEvent, 它继承自EventBase, 并提供了一个购物项集合
public class ShoppingCartSubmittedEvent : EventBase { public ShoppingCartSubmittedEvent() { Items = new List<ShoppingCartSubmittedItem>(); } public List<ShoppingCartSubmittedItem> Items { get; set; } } public class ShoppingCartSubmittedItem { public string ItemId { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
定义事件处理器接口
为了添加事件处理器,我们首先需要定义一个泛型接口类IEventHandler
public interface IEventHandler<T> where T : EventBase { void Run(T obj); Task RunAsync(T obj); }
这个泛型接口类的是泛型类型必须继承自EventBase类。接口提供了2个方法Run和RunAsync。 它们定义了该接口的实现类必须实现同一个处理逻辑的同步和异步方法。
为购物车提交事件编写事件处理器
有了事件处理器接口,接下来我们就可以开始为购物车提交事件添加事件处理器了。这里我们为了实现前面定义的逻辑,我们需要创建2个处理器CreateOrderHandler和ConfirmEmailSentHandler
CreateOrderHandler.cs
public class CreateOrderHandler : IEventHandler<ShoppingCartSubmittedEvent> { private IOrderManager _orderManager = null; public CreateOrderHandler(IOrderManager orderManager) { _orderManager = orderManager; } public void Run(ShoppingCartSubmittedEvent obj) { _orderManager.CreateNewOrder(new Models.DTOs.CreateOrderDTO { Items = obj.Items.Select(p => new Models.DTOs.NewOrderItemDTO { ItemId = p.ItemId, Name = p.Name, Price = p.Price }).ToList() }); } public Task RunAsync(ShoppingCartSubmittedEvent obj) { return Task.Run(() => { Run(obj); }); } }
代码解释:
在CreateOrderHandler的构造函数中,我们注入了IOrderManager接口对象,CreateNewOrder负责最终创建订单的工作
这里为了简化代码,我直接使用了Task.Run,并在其中调用了同步方法实现
ConfirmEmailSentHandler.cs