結合 AOP 輕松處理事件發布處理日志
Intro
前段時間,實現了 EventBus 以及 EventQueue 基於 Event 的事件處理,但是沒有做日志(EventLog)相關的部分,原本想增加兩個接口, 處理事件發布日志和事件處理日志,最近用了 AOP 的思想處理了 EntityFramework 的數據變更自動審計,於是想着事件日志也用 AOP 的思想來實現,而且可能用 AOP 來處理可能會更好一些,最近自己造了一個 AOP 的輪子 —— FluentAspects,下面的示例就以它來演示了,你也可以換成自己喜歡的 AOP 組件,思想是類似的
事件日志示例
事件發布日志
事件發布日志只需要攔截事件發布的方法調用即可,在發布事件時進行攔截,在攔截器中根據需要進行日志記錄即可
事件發布者接口定義:
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;
}
事件發布日志攔截器:
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("-------------------------------");
}
}
事件處理日志
事件處理器接口定義:
public interface IEventHandler
{
Task Handle(object eventData);
}
事件處理日志攔截器定義:
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("-------------------------------");
}
}
AOP 配置
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.UseFluentAspectsServiceProviderFactory(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();
More
事件發布示例,定義了一個發布事件的中間件:
// pageView middleware
app.Use((context, next) =>
{
var eventPublisher = context.RequestServices
.GetRequiredService<IEventPublisher>();
eventPublisher.Publish(new PageViewEvent()
{
Path = context.Request.Path.Value,
});
return next();
});
事件處理示例是用一個消息隊列的模式來處理的,示例和前面的事件的文章類似,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);
}
}
}
完整的示例代碼可以從https://github.com/WeihanLi/WeihanLi.Common/blob/dev/samples/AspNetCoreSample 獲取
OverMore
之前在微軟的 EShopOnContainers 項目里又看到類似下面這樣的代碼,在發布事件的時候包裝一層 try ... catch 來記錄事件發布日志,相比之下,本文示例中的這種方式更為簡潔,代碼更清爽