諾禾-EventBus/EventQueue 再思考

最近把 Event 相關的邏輯做了一個重構,修正 EventStore,引入了 IEventHandlerFactory,重新設計了 Event 相關的組件

重構後的 Event
Event: 事情的籠統定義
EventHandler:事情處置器籠統定義
EventHandlerFactory:事情處置器工廠,用來依據事情類型獲取事情處置器(新增)
EventPublisher:事情發佈器,用於事情發佈
EventSubscriber:事情訂閱器,用於管理事情的訂閱
EventSubscriptionManager:事情訂閱管理器,在 EventSubscriber 的根底上增加了一個依據事情類型獲取事情訂閱器類型的辦法
EventBus:事情總線,由 EventPubliser 和 EventSubscriber 組合而成,用來比擬便當的做事情發佈和訂閱
EventQueue:事情隊列,希望某些音訊次第處置的時分能夠思索用 EventQueue 的形式
EventStore:事情存儲,事情的耐久化存儲(在之前的版本里,EventStore 實踐作用是一個 EventSubscriptionManager,在最近的版本更新中已修正)
以上 EventSubscriber 和 EventSubscriptionManager 普通不直接用,普通用 EventBus 來處置即可

EventHandlerFactory
這次引入了 EventHandlerFactory 用來籠統獲取 EventHandler 的邏輯,原來的設計裏是在處置 Event 的時分獲取 EventHandler 的類型,然後從依賴注入框架中獲取或創立新的 event handler 實例之後再調用 EventHandler 的 Handle 辦法處置事情,有一些冗餘

運用 EventHandlerFactory 之後就能夠直接獲取一個 EventHandler 實例匯合,詳細是實例化還是從依賴注入中獲取就由 EventHandlerFactory 來決議了,這樣就能夠對依賴注入很友好,關於基於內存的簡單 EventBus 來說,在效勞註冊之後就不需求再調用 Subscribe 去顯式訂閱了,由於再註冊效勞的時分就曾經隱式完成了訂閱的邏輯,這樣實踐就不需求 EventSubscriptionManager 來管理訂閱了,訂閱信息都在依賴注入框架內部,比方說 CounterEvent,要獲取它的訂閱信息,我只需求從依賴注入框架中獲取 IEventHandler 的實例即可,實踐就替代了原先 “EventStoreInMemory”,如今的 EventSubscriptionManagerInMemory

基於依賴注入的 EventHandlerFactory 定義:

public sealed class DependencyInjectionEventHandlerFactory : IEventHandlerFactory
{
private readonly IServiceProvider _serviceProvider;
public DependencyInjectionEventHandlerFactory(IServiceProvider serviceProvider = null)
{
_serviceProvider = serviceProvider ?? DependencyResolver.Current;
}
public ICollection GetHandlers(Type eventType)
{
var eventHandlerType = typeof(IEventHandler<>).MakeGenericType(eventType);
return _serviceProvider.GetServices(eventHandlerType).Cast().ToArray();
}
}
假如不運用依賴注入,也能夠依據 IEventSubscriptionManager 訂閱信息來完成:

public sealed class DefaultEventHandlerFactory : IEventHandlerFactory
{
private readonly IEventSubscriptionManager _subscriptionManager;
private readonly ConcurrentDictionary<Type, ICollection> _eventHandlers = new ConcurrentDictionary<Type, ICollection>();
private readonly IServiceProvider _serviceProvider;
public DefaultEventHandlerFactory(IEventSubscriptionManager subscriptionManager, IServiceProvider serviceProvider = null)
{
_subscriptionManager = subscriptionManager;
_serviceProvider = serviceProvider ?? DependencyResolver.Current;
}
public ICollection GetHandlers(Type eventType)
{
var eventHandlers = _eventHandlers.GetOrAdd(eventType, type =>
{
var handlerTypes = _subscriptionManager.GetEventHandlerTypes(type);
var handlers = handlerTypes
.Select(t => (IEventHandler)_serviceProvider.GetServiceOrCreateInstance(t))
.ToArray();
return handlers;
});
return eventHandlers;
}
}
EventQueue Demo
來看一下 EventQueue 的示例,示例基於 asp.net core 的,定義了一個 HostedService 來完成一個 EventConsumer 來消費 EventQueue 中的事情信息

EventConsumer 定義如下:

public class EventConsumer : BackgroundService
{
private readonly IEventQueue _eventQueue;
private readonly IEventHandlerFactory _eventHandlerFactory;
public EventConsumer(IEventQueue eventQueue, IEventHandlerFactory eventHandlerFactory)
{
_eventQueue = eventQueue;
_eventHandlerFactory = eventHandlerFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var queues = await _eventQueue.GetQueuesAsync();
if (queues.Count > 0)
{
await queues.Select(async q =>
{
var @event = await _eventQueue.DequeueAsync(q);
if (null != @event)
{
var handlers = _eventHandlerFactory.GetHandlers(@event.GetType());
if (handlers.Count > 0)
{
await handlers
.Select(h => h.Handle(@event))
.WhenAll()
;
}
}
})
.WhenAll()
;
}
await Task.Delay(1000, stoppingToken);
}
}
}
定義 PageViewEvent 和 PageViewEventHandler,用來記載和處置懇求訪問記載

public class PageViewEvent : EventBase
{
}
public class PageViewEventHandler : EventHandlerBase<PageViewEvent>
{
public static int Count;
public override Task Handle(PageViewEvent @event)
{
Interlocked.Increment(ref Count);
return Task.CompletedTask;
}
}
事情很簡單,事情處置也只是增加了 PageViewEventHandler 內定義的 Count。

效勞註冊:

// 註冊事情中心組件
// 會註冊 EventBus、EventHandlerFactory、EventQueue 等
services.AddEvents()
// 註冊 EventHanlder
.AddEventHandler<PageViewEvent, PageViewEventHandler>()
;
// 註冊 EventQueuePubliser,默許註冊的 IEventPublisher 是 EventBus
services.AddSingleton<IEventPublisher, EventQueuePublisher>();
// 註冊 EventConsumer
services.AddHostedService();
事情發佈,定義了一箇中間件來發布 PageViewEvent,定義如下:

// pageView middleware
app.Use((context, next) =>
{
var eventPublisher = context.RequestServices.GetRequiredService();
eventPublisher.Publish(new PageViewEvent());
return next();
});
然後定義一個接口來獲取上面定義的 PageViewEventHandler 中的 Count

[Route("api/[controller]")]
public class EventsController : ControllerBase
{
[HttpGet("pageViewCount")]
public IActionResult Count()
{
return Ok(new { Count = PageViewEventHandler.Count });
}
}
運轉起來之後,訪問幾次接口,看上面的接口返回 Count 能否會增加,正常的話每訪問一次接口就會增加 1,併發訪問問題也不大,由於每個事情都是次第處置的,即便併發訪問也沒有關係,事情發佈之後,在隊列裏都是次第處置的,這也就是引入事情隊列的目的(彷彿上面的原子遞增沒什麼用了...) 假如沒看到了增加,稍等一會兒再訪問試試,事情處置會遲到,但總會處置,畢竟是異步處置的,有些延遲很正常,而且上面我們還有一個 1s 的延遲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章