標題: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的開閉原則。