Nop架構中,可以看到多處用到事件機制,特别是緩存的更新,有些人可能會疑惑,這麼做解決了什麼問題?
假如我們有這麼一個場景,一個客戶注冊後,我們會更新一下緩存,然後發送一封注冊郵件,正常的做法是:
void InsertCustomer()
{
1. 新增到資料庫
2. 插入到緩存
3. 發送注冊郵件
}
這樣做本來沒有什麼問題,但這樣的代碼會緊緊的耦合在一起,如果想再加一個注冊後發送優惠券的動作,可能就會再裡面增加一個發送優惠券的動作,是以的業務都牢牢的綁定在了新增方法裡面,Nop怎麼做的呢
void Insert()
{
1. 新增到資料庫
2. 釋出一條新增消息
}
class CacheConsumer: IConsumer<EntityInsertedEvent<Customer>>
{
public void HandleEvent(EntityUpdatedEvent<Setting> eventMessage)
{
//新增緩存
}
}
class MailConsumer: IConsumer<EntityInsertedEvent<Customer>>
{
public void HandleEvent(EntityUpdatedEvent<Setting> eventMessage)
{
//發送郵件
}
}
具體來解釋執行流程:
- 新增一條資料後,釋出一條消息到消息管理器
- 所有訂閱該新增消息的消費者都會收到消息
- 消費者執行具體的事件,比如新增緩存、發送郵件等
Nop的技術實作可能會更複雜了一點,它沒有采用傳統的RabbitMQ等第三方消息隊列,而是結合Autofac的依賴注入,巧妙的實作了這一設計:
- IEventPublisher事件釋出接口
- EventPublisher實作IEventPublisher的類,主要功能包括釋出事件并通知訂閱者
- IConsumer消費者(事件訂閱者)接口
- ISubscriptionService訂閱者接口,解析所有的訂閱者
- SubscriptionService的具體實作
接下來我們看下具體的實作類,以CategoryService為例,已删除不必要的細節:
public virtual void InsertCategory(Category category)
{
_categoryRepository.Insert(category);
//釋出一條消息
_eventPublisher.EntityInserted(category);
}
而訂閱該消息的類PriceCacheEventConsumer,代碼如下:
public partial class PriceCacheEventConsumer : IConsumer<EntityInsertedEvent<Category>>
{
public void HandleEvent(EntityInsertedEvent<Category> eventMessage)
{
_cacheManager.RemoveByPattern(NopCatalogDefaults.ProductCategoryIdsPatternCacheKey);
}
}
上述代碼已删除幹擾因素,我們看到該類繼承于IConsumer
接下來我們看下,事件釋出類EventPublisher做的哪些事情:
/// <summary>
/// 釋出到消費者
/// </summary>
protected virtual void PublishToConsumer<T>(IConsumer<T> x, T eventMessage)
{
try
{
x.HandleEvent(eventMessage); //執行消費者訂閱方法
}
catch (Exception exc)
{
}
}
/// <summary>
/// 釋出事件,注意,是整個實作的核心
/// </summary>
public virtual void Publish<T>(T eventMessage)
{
//利用控制反轉IOC技術,查找所有消費者類
var subscribers = _subscriptionService.GetSubscriptions<T>()
.Where(subscriber => PluginManager.FindPlugin(subscriber.GetType())?.Installed ?? true).ToList();
//調用PublishToConsumer,執行對應消費者類的方法,這個是關鍵
subscribers.ForEach(subscriber => PublishToConsumer(subscriber, eventMessage));
}
事件釋出服務:SubscriptionService,該類是個工具類,提供了根據類型查找消費者的方法GetSubscriptions
public IList<IConsumer<T>> GetSubscriptions<T>()
{
return EngineContext.Current.ResolveAll<IConsumer<T>>().ToList();
}
上述類裡面的EngineContext,Nop引擎管理器,Current指的是目前引擎,ResolveAll從容器中取出消費類,具體技術檢視Autofac
總結一下:
- 整個代碼實作了解耦,不再互相依賴
- 釋出消息訂閱,并沒有直接采用第三方消息隊列RabbitMQ,是以沒有單獨的監控端
- 采用了控制反轉技術,巧妙的解決了查找消費者的困難,如果采用反射,性能會下降