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,所以没有单独的监控端
- 采用了控制反转技术,巧妙的解决了查找消费者的困难,如果采用反射,性能会下降