前段時間,實現了 EventBus 以及 EventQueue 基於 Event 的事件處理,可是沒有作日誌(EventLog)相關的部分,本來想增長兩個接口, 處理事件發佈日誌和事件處理日誌,最近用了 AOP 的思想處理了 EntityFramework 的數據變動自動審計,因而想着事件日誌也用 AOP 的思想來實現,並且可能用 AOP 來處理可能會更好一些,最近本身造了一個 AOP 的輪子 —— FluentAspects,下面的示例就以它來演示了,你也能夠換成本身喜歡的 AOP 組件,思想是相似的html
事件發佈日誌只須要攔截事件發佈的方法調用便可,在發佈事件時進行攔截,在攔截器中根據須要進行日誌記錄便可git
事件發佈者接口定義:github
public interface IEventPublisher { /// <summary> /// publish an event /// </summary> /// <typeparam name="TEvent">event type</typeparam> /// <param name="event">event data</param> /// <returns>whether the operation succeed</returns> bool Publish<TEvent>(TEvent @event) where TEvent : class, IEventBase; /// <summary> /// publish an event async /// </summary> /// <typeparam name="TEvent">event type</typeparam> /// <param name="event">event data</param> /// <returns>whether the operation succeed</returns> Task<bool> PublishAsync<TEvent>(TEvent @event) where TEvent : class, IEventBase; }
事件發佈日誌攔截器:app
public class EventPublishLogInterceptor : AbstractInterceptor { public override async Task Invoke(IInvocation invocation, Func<Task> next) { Console.WriteLine("-------------------------------"); Console.WriteLine($"Event publish begin, eventData:{invocation.Arguments.ToJson()}"); var watch = Stopwatch.StartNew(); try { await next(); } catch (Exception ex) { Console.WriteLine($"Event publish exception({ex})"); } finally { watch.Stop(); Console.WriteLine($"Event publish complete, elasped:{watch.ElapsedMilliseconds} ms"); } Console.WriteLine("-------------------------------"); } }
事件處理器接口定義:async
public interface IEventHandler { Task Handle(object eventData); }
事件處理日誌攔截器定義:ide
public class EventHandleLogInterceptor : IInterceptor { public async Task Invoke(IInvocation invocation, Func<Task> next) { Console.WriteLine("-------------------------------"); Console.WriteLine($"Event handle begin, eventData:{invocation.Arguments.ToJson()}"); var watch = Stopwatch.StartNew(); try { await next(); } catch (Exception ex) { Console.WriteLine($"Event handle exception({ex})"); } finally { watch.Stop(); Console.WriteLine($"Event handle complete, elasped:{watch.ElapsedMilliseconds} ms"); } Console.WriteLine("-------------------------------"); } }
Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(builder => { builder.UseStartup<Startup>(); }) .UseFluentAspectServiceProviderFactory(options => { // 攔截器配置 // 攔截 `IEventPublisher` 日誌,註冊事件發佈日誌攔截器 options .InterceptType<IEventPublisher>() .With<EventPublishLogInterceptor>(); // 攔截 `IEventHandler`,註冊事件處理日誌攔截器 options.InterceptType<IEventHandler>() .With<EventHandleLogInterceptor>(); }, builder => { // 默認使用默認實現來生成代理,如今提供了 Castle 和 AspectCore 的擴展,也能夠本身擴展實現自定義代理生成方式 // 取消註釋使用 Castle 來生成代理 //builder.UseCastleProxy(); }, t => t.Namespace?.StartsWith("WeihanLi") == false // 要忽略的類型斷言 ) .Build() .Run();
事件發佈示例,定義了一個發佈事件的中間件:ui
// pageView middleware app.Use((context, next) => { var eventPublisher = context.RequestServices .GetRequiredService<IEventPublisher>(); eventPublisher.Publish(new PageViewEvent() { Path = context.Request.Path.Value, }); return next(); });
事件處理示例是用一個消息隊列的模式來處理的,示例和前面的事件的文章相似,EventConsumer
是一個後臺任務,完整代碼示例以下:spa
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); } } }
完整的示例代碼能夠從https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/AspNetCoreSample 獲取pwa
以前在微軟的 EShopOnContainers 項目裏又看到相似下面這樣的代碼,在發佈事件的時候包裝一層 try ... catch 來記錄事件發佈日誌,相比之下,本文示例中的這種方式更爲簡潔,代碼更清爽代理