标题:ASP.NET Core中实现单体程序的事件发布/订阅
做者:Lamond Lu
地址:http://www.javashuo.com/article/p-phbjtsuc-ds.html
项目源代码:https://github.com/lamondlu/EventHandlerInSingleApplicationhtml
事件发布/订阅是一种很是强大的模式,它能够帮助业务组件间实现彻底解耦,不一样的业务组件只依赖事件,只关注哪些事件是须要本身处理的,而不用关注谁来处理本身发布事件,事件追溯(Event Sourcing)也是基于事件发布/订阅的。在微服务架构中,事件发布/订阅有很是多的应用场景。今天我给你们分享一个基于ASP.NET Core的单体程序使用事件发布/订阅的例子,针对分布式项目的事件发布/订阅比较复杂,难点是事务处理,后续我会另写一篇博文来演示。git
当前咱们有一个基于ASP.NET Core的电子商务系统,在项目的初期,业务很是简单,只有一个购物车模块和一个订单模块,全部的代码都放在一个项目中。github
整个项目使用了一个简单的三层架构。数据库
这里当用户提交购物车的时候,程序会在ShoppingCartManager
类的SubmitShoppingCart
方法中执行3个操做c#
代码以下:api
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
事件处理器会负责发送邮件。好的,下面让咱们来一步一步实现以上描述的代码。app
这里咱们首先定义一个事件基类,其中暂时只添加了一个属性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
public class ConfirmEmailSentHandler : IEventHandler<ShoppingCartSubmittedEvent> { public void Run(ShoppingCartSubmittedEvent obj) { Console.WriteLine("Confirm Email Sent."); } public Task RunAsync(ShoppingCartSubmittedEvent obj) { return Task.Run(() => { Console.WriteLine("Confirm Email Sent."); }); } }
代码解释:
- 这个处理类很是简单,为了简化代码,我仅输出了一行文原本表示实际须要运行的代码。
OrderManager
类添加建立订单方法IOrderManager.cs
public interface IOrderManager { string CreateNewOrder(CreateOrderDTO dto); }
OrderManager.cs
public class OrderManager : IOrderManager { private IOrderRepository _orderRepository; public OrderManager(IOrderRepository orderRepository) { _orderRepository = orderRepository; } public string CreateNewOrder(CreateOrderDTO dto) { var orderId = _orderRepository.CreatOrder(dto); Console.WriteLine($"One order created: {JsonConvert.SerializeObject(dto)}"); return orderId; } }
EventHandlerContainer
下面咱们来编写最核心的事件处理器容器。在这里咱们的事件处理器容器完成了3个功能
public class EventHandlerContainer { private IServiceProvider _serviceProvider = null; private static Dictionary<string, List<Type>> _mappings = new Dictionary<string, List<Type>>(); public EventHandlerContainer(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public static void Subscribe<T, THandler>() where T : EventBase where THandler : IEventHandler<T> { var name = typeof(T).Name; if (!_mappings.ContainsKey(name)) { _mappings.Add(name, new List<Type> { }); } _mappings[name].Add(typeof(THandler)); } public static void Unsubscribe<T, THandler>() where T : EventBase where THandler : IEventHandler<T> { var name = typeof(T).Name; _mappings[name].Remove(typeof(THandler)); if (_mappings[name].Count == 0) { _mappings.Remove(name); } } public void Publish<T>(T o) where T : EventBase { var name = typeof(T).Name; if (_mappings.ContainsKey(name)) { foreach (var handler in _mappings[name]) { var service = (IEventHandler<T>)_serviceProvider.GetService(handler); service.Run(o); } } } public async Task PublishAsync<T>(T o) where T : EventBase { var name = typeof(T).Name; if (_mappings.ContainsKey(name)) { foreach (var handler in _mappings[name]) { var service = (IEventHandler<T>)_serviceProvider.GetService(handler); await service.RunAsync(o); } } } }
代码解释:
- 这里我没有直接订阅事件处理器的实例,并且订阅了事件处理器的类型
- 多个事件处理器能够订阅同一个事件
EventHandlerContainer
的构造函数中,咱们注入了一个IServiceProvider
,咱们可使用它来得到对应事件处理器的实例。
如今咱们来Startup.cs
的ConfigureServices
方法,这里咱们须要进行服务注册,并完成事件订阅。
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddScoped<IOrderManager, OrderManager>(); services.AddScoped<IShoppingCartManager, ShoppingCartManager>(); services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>(); services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddScoped<CreateOrderHandler>(); services.AddScoped<ConfirmEmailSentHandler>(); services.AddScoped<EventHandlerContainer>(); EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, CreateOrderHandler>(); EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, ConfirmEmailSentHandler>(); }
注意:这里保证一个Api请求中的全部数据库操做在一个事务里,这里咱们使用
Scoped
做用域。这样咱们就能够在调用工做单元IUnitOfWork
接口的Save
代码中启用事务。
最后咱们来修改ShoppingCartManager
, 改用发布事件的方式来完成后续建立订单和发送邮件的功能。
public void SubmitShoppingCart(string shoppingCartId) { var shoppingCart = _unitOfWork.ShoppingCartRepository .GetShoppingCart(shoppingCartId); _unitOfWork.ShoppingCartRepository .SubmitShoppingCart(shoppingCartId); _container.Publish(new ShoppingCartSubmittedEvent() { Items = shoppingCart .Items .Select(p => new ShoppingCartSubmittedItem { ItemId = p.ItemId, Name = p.Name, Price = p.Price }) .ToList() }); _unitOfWork.Save(); }
这样ShoppingCartManager
就只须要关注购物车状态的变动,而不须要关注发送确认邮件和建立订单了。
如今让咱们启动项目,
首先咱们使用[POST] /api/shoppingCarts来添加一个新的购物车, 这个API会返回当前购物车的Id
而后咱们使用[PUT] /api/shoppingCarts/ShoppingCart_636872897140555966来模拟提交购物车,程序返回操做成功
最后咱们查看一下控制台的输出日志
2个事件处理器都被正确触发了。
至此咱们的代码重构完成。 最终的代码中,SubmitShoppingCart
方法,仅负责修改购物车状态并发布一个购物车提交的事件。生成订单和发送邮件的功能代码都被移动到了独立的处理类中。
这样的方式的好处不单单是完成了代码的解耦,针对后续的扩展也很是有利,想一想一下,若是在将来当前项目需求追加这样一个功能,当提交购物车的时候,除了要发送确认邮件,还要发送手机短信。这时候你根本不须要去修改ShoppingCartManager
类,你只须要针对ShoppingCartSubmittedEvent
在再添加一个新的事件处理器便可,这也知足的SOLID的开闭原则。