nopCommerce 3.9 事件機制簡介,nop中如何使用生產者消費者模式進行事件擴展. IEventPublisher介面、IConsumer ...
一.nop事件機制簡介
應用場景:客戶支付成功後,需要發送簡訊、郵件告知客戶訂單支付成功(簡訊、郵件由不同模塊實現)
實現方法: 1.定義支付成功OrderPaidEvent事件。
2.定義簡訊,郵箱兩個消費者共同監聽OrderPaidEvent事件,並實現相關業務。
3.當客戶支付成功後生產者發送OrderPaidEvent事件。
4.消費者接收到OrderPaidEvent事件後,簡訊和郵箱消費者分別執行自己的業務。
nop事件機制使用到“生產者/消費者”模式。生產者只負責發佈事件,並不需要關心誰來處理,相反消費者只用來處理事件。那生產者和消費者是如何進行關聯的呢?nop實現是非常簡單的,通過泛型來定義一個事件類,如果生產者和消費者都使用同一個事件類,那麼就關聯到一起了稱之為訂閱。負責實現事件機制的部分稱之為緩衝區,緩衝區的作用是通過解耦的方式實現消息機制。生產者和消費者是一對多的關係。下圖簡單介紹下生產者消費者關係。
二.nop事件相關介面
生產者介面:Nop.Services.Events.IEventPublisher
消費者介面:Nop.Services.Events.IConsumer<T>
事件訂閱介面:Nop.Services.Events.ISubscriptionService
IEventPublisher介面Publish<T>(T eventMessage)方法用於發佈事件(生產者)。
IConsumer<T>介面HandleEvent(T eventMessage)方法用於處理事件(消費者)。
兩者之間的關係由T泛型來關聯,稱之為事件,簡單的說T類型相同則兩者關聯訂閱成功。
ISubscriptionService介面GetSubscriptions<T>()方法返回IList<IConsumer<T>>集合,該集合保存了消費者。
介面實現如下圖:
1 using System; 2 using System.Linq; 3 using Nop.Core.Infrastructure; 4 using Nop.Core.Plugins; 5 using Nop.Services.Logging; 6 7 namespace Nop.Services.Events 8 { 9 /// <summary> 10 /// Evnt publisher 11 /// </summary> 12 public class EventPublisher : IEventPublisher 13 { 14 private readonly ISubscriptionService _subscriptionService; 15 16 /// <summary> 17 /// Ctor 18 /// </summary> 19 /// <param name="subscriptionService"></param> 20 public EventPublisher(ISubscriptionService subscriptionService) 21 { 22 _subscriptionService = subscriptionService; 23 } 24 25 /// <summary> 26 /// Publish to cunsumer 27 /// </summary> 28 /// <typeparam name="T">Type</typeparam> 29 /// <param name="x">Event consumer</param> 30 /// <param name="eventMessage">Event message</param> 31 protected virtual void PublishToConsumer<T>(IConsumer<T> x, T eventMessage) 32 { 33 //Ignore not installed plugins 34 var plugin = FindPlugin(x.GetType()); 35 if (plugin != null && !plugin.Installed) 36 return; 37 38 try 39 { 40 //消費者處理方法 41 x.HandleEvent(eventMessage); 42 } 43 catch (Exception exc) 44 { 45 //log error 46 var logger = EngineContext.Current.Resolve<ILogger>(); 47 //we put in to nested try-catch to prevent possible cyclic (if some error occurs) 48 try 49 { 50 logger.Error(exc.Message, exc); 51 } 52 catch (Exception) 53 { 54 //do nothing 55 } 56 } 57 } 58 59 /// <summary> 60 /// Find a plugin descriptor by some type which is located into its assembly 61 /// </summary> 62 /// <param name="providerType">Provider type</param> 63 /// <returns>Plugin descriptor</returns> 64 protected virtual PluginDescriptor FindPlugin(Type providerType) 65 { 66 if (providerType == null) 67 throw new ArgumentNullException("providerType"); 68 69 if (PluginManager.ReferencedPlugins == null) 70 return null; 71 72 foreach (var plugin in PluginManager.ReferencedPlugins) 73 { 74 if (plugin.ReferencedAssembly == null) 75 continue; 76 77 if (plugin.ReferencedAssembly.FullName == providerType.Assembly.FullName) 78 return plugin; 79 } 80 81 return null; 82 } 83 84 /// <summary> 85 /// 發送事件 86 /// </summary> 87 /// <typeparam name="T">Type</typeparam> 88 /// <param name="eventMessage">Event message</param> 89 public virtual void Publish<T>(T eventMessage) 90 { 91 var subscriptions = _subscriptionService.GetSubscriptions<T>();//獲取訂閱消費者 92 subscriptions.ToList().ForEach(x => PublishToConsumer(x, eventMessage)); 93 } 94 95 } 96 } 97EventPublisher
1 using System.Collections.Generic; 2 using Nop.Core.Infrastructure; 3 4 namespace Nop.Services.Events 5 { 6 /// <summary> 7 /// 事件訂閱服務 8 /// </summary> 9 public class SubscriptionService : ISubscriptionService 10 { 11 /// <summary> 12 /// 獲取事件訂閱 13 /// </summary> 14 /// <typeparam name="T">Type</typeparam> 15 /// <returns>Event consumers</returns> 16 public IList<IConsumer<T>> GetSubscriptions<T>() 17 { 18 return EngineContext.Current.ResolveAll<IConsumer<T>>(); 19 } 20 } 21 } 22
二.消費者IConsermer<T>註冊
應用啟動時Nop.Web.Framework.DependencyRegistrar中將所有實現IConsumer<T>介面的類註冊到ioc容器中。
通過EngineContext.Current.ResolveAll<IConsumer<T>>(),就可以獲取到某個消息(T)的訂閱了。
1 //註冊事件消費者 2 var consumers = typeFinder.FindClassesOfType(typeof(IConsumer<>)).ToList(); 3 foreach (var consumer in consumers) 4 { 5 builder.RegisterType(consumer) 6 .As(consumer.FindInterfaces((type, criteria) => 7 { 8 var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition()); 9 return isMatch; 10 }, typeof(IConsumer<>))) 11 .InstancePerLifetimeScope(); 12 }
三.創建消費者
結合上邊提到的應用場景,我們創建訂閱OrderPaidEvent事件來處理簡訊通知的消費者。
創建OrderPaidSMSEventConsumer類
1 using System; 2 using Nop.Core; 3 using Nop.Core.Domain.Orders; 4 using Nop.Core.Plugins; 5 using Nop.Services.Events; 6 using Nop.Services.Orders; 7 8 namespace Nop.Plugin.SMS 9 { 10 public class OrderPaidSMSEventConsumer : IConsumer<OrderPaidEvent> 11 { 12 13 private readonly IOrderService _orderService; 14 15 public OrderPaidSMSEventConsumer( 16 IOrderService orderService, 17 IStoreContext storeContext) 18 { 19 this._orderService = orderService; 20 this._storeContext = storeContext; 21 } 22 23 /// <summary> 24 /// 事件處理. 25 /// </summary> 26 /// <param name="eventMessage">The event message.</param> 27 public void HandleEvent(OrderPaidEvent eventMessage) 28 { 29 30 var order = eventMessage.Order;//獲取訂單 31 32 //發送短息通知代碼 33 //.................... 34 } 35 } 36 }
OrderPaidSMSEventConsumer類繼承 IConsumer<OrderPaidEvent>,OrderPaidEvent就是事件類,維護生產者與消費者之間的訂閱關係。事件類名稱可以自定義,代表了一個事件。
接下來我們再創建一個郵件處理的消費者OrderPaidEmailEventConsumer類,同樣繼承了ICnsumer<OrderPaidEvent>,說明我們訂閱的是也是OrderPaidEvent事件。
1 using System; 2 using Nop.Core; 3 using Nop.Core.Domain.Orders; 4 using Nop.Core.Plugins; 5 using Nop.Services.Events; 6 using Nop.Services.Orders; 7 8 namespace Nop.Plugin.Email 9 { 10 public class OrderPaidEmailEventConsumer : IConsumer<OrderPaidEvent> 11 { 12 13 private readonly IOrderService _orderService; 14 private readonly IStoreContext _storeContext; 15 16 public OrderPaidEmailEventConsumer( 17 IOrderService orderService, 18 IStoreContext storeContext) 19 { 20 21 this._orderService = orderService; 22 this._storeContext = storeContext; 23 } 24 25 /// <summary> 26 /// 郵件處理 27 /// </summary> 28 /// <param name="eventMessage">The event message.</param> 29 public void HandleEvent(OrderPaidEvent eventMessage) 30 { 31 32 33 var order = eventMessage.Order; 34 35 //發送郵件通知客戶 36 //............................ 37 } 38 } 39 }
四.生產消息
我們已經創建了兩個訂閱了OrderPaidEvent事件的消費者,現在我們看看當客戶支付完成時我們是如何通知消費者的。
Nop.Services.OrderProcessingService類中
_eventPublisher.Publish(new OrderPaidEvent(order))方法發送了OrderPaidEvent事件。這時候上邊訂閱OrderPaidEvent事件的消費(簡訊、郵件)就會處理消息了。
五.nop中常用的事件整理
消費者,主要還是處理緩存
Nop.Web.Infrastructure.Cache.ModelCacheEventConsumer:前臺模型相關
Nop.Admin.Infrastructure.Cache.ModelCacheEventConsumer:後臺模型相關
Nop.Services.Discounts.Cache.DiscountEventConsumer:折扣相關
Nop.Services.Catalog.Cache.PriceCacheEventConsumer:價格相關
Nop.Services.Customers.Cache.CustomerCacheEventConsumer:密碼修改
生產者,下圖總結了nop 3.9 源碼中自帶的事件及所在的類,大部分是未實現對應的消費者。
nop只是在相關的地方留下事件位置,方便我們二次開發的時候進行擴展。
六.總結
1.生產者需要繼承IEventPublisher介面。
2.消費者需要繼承IConsumer<T>介面。
3.消費者通過事件類訂閱到生產者,訂閱實現參見ISubscriptionService介面。
nop事件機制實現很簡單,有興趣的朋友可以用RabbitMQ進行消息的擴展。
文中有錯誤的理解和不正確的觀點,請留言,一起交流共同進步。
本文地址:http://www.cnblogs.com/yaoshangjin/p/7234522.html
本文為大波浪原創、轉載請註明出處。