天天看点

NopCommerce事件发布订阅机制详解

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)
    {
        //发送邮件
    }
}
           

具体来解释执行流程:

  1. 新增一条数据后,发布一条消息到消息管理器
  2. 所有订阅该新增消息的消费者都会收到消息
  3. 消费者执行具体的事件,比如新增缓存、发送邮件等

Nop的技术实现可能会更复杂了一点,它没有采用传统的RabbitMQ等第三方消息队列,而是结合Autofac的依赖注入,巧妙的实现了这一设计:

  1. IEventPublisher事件发布接口
  2. EventPublisher实现IEventPublisher的类,主要功能包括发布事件并通知订阅者
  3. IConsumer消费者(事件订阅者)接口
  4. ISubscriptionService订阅者接口,解析所有的订阅者
  5. 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

总结一下:

  1. 整个代码实现了解耦,不再相互依赖
  2. 发布消息订阅,并没有直接采用第三方消息队列RabbitMQ,所以没有单独的监控端
  3. 采用了控制反转技术,巧妙的解决了查找消费者的困难,如果采用反射,性能会下降