淺入 ABP 系列(4):事件匯流排
阿新 • • 發佈:2020-09-16
# 淺入 ABP 系列(4):事件匯流排
版權護體©作者:痴者工良,微信公眾號轉載文章需要 《NCC開源社群》同意。
[TOC]
這一篇將來學習 ABP 中的事件匯流排,然後結合在我們的基架專案中,逐漸構建一個完整的系統。
原始碼地址:https://github.com/whuanle/AbpBaseStruct
## 事件匯流排
### 關於事件匯流排
ABP 中,為了方便程序間通訊,給開發者提供了一個叫 `事件匯流排` 的功能,事件匯流排分為 `本地事件匯流排`、`分散式事件匯流排`,本篇文章講的是 `本地事件匯流排`,系列教程中暫時不考慮講解 `分散式事件匯流排`。
`事件匯流排` 需要使用 `Volo.Abp.EventBus` 庫,ABP 包中自帶,不需要額外引入。
事件匯流排是通過 訂閱-釋出 形式使用的,某一方只需要按照格式推送事件,而不需要關注是誰接收了事件和如何處理事件。
你可以參考官方文件:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus
### 為什麼需要這個東西
首先列舉一下,你工作開發的專案中,編寫 控制器時,是不是有這幾種程式碼。
```
// 記錄日誌 1
Task.Run(()=>
{
_apiLog.Info($"xxxxxxxx");
});
```
```
// 記錄日誌 2
catch(Exception ex)
{
_apiLog.Error(ex);
}
```
```
// 記錄日誌 3
_apiLog.Info($"登陸資訊:使用者 [{userName}({clientAdrress})]\);
```
筆者認為,改善的上述問的方法之一是將函式的功能跟記錄日誌分開,函式執行任務時,只需要把狀態和資訊通過事件匯流排推送,而不需要了關注應該如何處理這些內容。
另外,還有當函式執行某些步驟時,產生了事件,開發者喜歡 `new Thread` 一個新的執行緒去執行別的任務,或者 `Task.Run`。
其實,通過事件匯流排,我們更加好地隔離程式碼,遵從 `單一職責原則` 。當然還有很多方面值得使用事件匯流排,這裡我們就不再扯淡了。
前面,我們編寫了全域性異常攔截器,還有日誌元件,這一篇我們將通過事件匯流排,將 Web 程式的一些部件組合起來。
### 事件匯流排建立過程
#### 訂閱事件
建立一個服務來訂閱事件,當程式中發生某種事件時,此服務將被呼叫。
事件服務必須繼承 `ILocalEventHandler` 介面,並實現以下函式:
```csharp
Task HandleEventAsync(TEvent eventData);
```
一個系統中,事件服務可以有多個,每個服務的 `TEvent` 型別不能相同,因為 `TEvent` 的型別是呼叫服務的標識。當發生 `TEvent` 事件後,系統通過 `TEvent` 去找到這個服務。
事件服務建立完畢後,需要加入到依賴注入中,你可以多繼承一個 `ITransientDependency` 介面,然後統一掃描程式集加入到 依賴注入容器中(第三篇提到過)。
#### 事件
即上面提到的 `TEvent`。
假設有一個系統中所有的事件服務都放到一個容器中,釋出者只能傳遞一個事件,而不能指定誰來提供響應服務。
容器是通過 `TEvent` 來查詢服務的。
事件就是一個模型類,也可以使用 `int`或者 `string` 等簡單型別(請不要用簡單型別做事件),用於傳遞資訊。
一般使用 `Event` 做字尾。
#### 釋出事件
如果需要釋出一個事件,只需要注入 `ILocalEventBus` 即可。
```csharp
private readonly ILocalEventBus _localEventBus;
public MyService(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
```
然後釋出事件:
```csharp
await _localEventBus.PublishAsync(
new TEvent
{
... ...
}
);
```
## 全域性異常加入事件匯流排功能
### 建立事件
在 `AbpBase.Web` 中,建立一個 `Handlers` 目錄,再在 `Handlers` 目錄下,建立 `HandlerEvents` 目錄。
然後在 `HandlerEvents` 目錄,建立一個 `CustomerExceptionEvent.cs` 檔案。
`CustomerExceptionEvent` 作為一個異常事件,用於傳遞異常的資訊,而不僅僅是將 `Exception ex` 記錄就了事。
其檔案內容如下:
```csharp
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
namespace AbpBase.Application.Handlers.HandlerEvents
{
///
/// 全域性異常推送事件
///
public class CustomerExceptionEvent
{
///
/// 只記錄異常
///
///
public CustomerExceptionEvent(Exception ex)
{
Exception = ex;
}
///
/// 此異常發生時,使用者請求的路由地址
///
///
///
public CustomerExceptionEvent(Exception ex, string actionRoute)
{
Exception = ex;
Action = actionRoute;
}
///
/// 此異常發生在哪個型別的方法中
///
///
///
public CustomerExceptionEvent(Exception ex, MethodBase method)
{
Exception = ex;
MethodInfo = (MethodInfo)method;
}
///
/// 記錄異常資訊
///
///
///
///
public CustomerExceptionEvent(Exception ex, string actionRoute, MethodBase method)
{
Exception = ex;
Action = actionRoute;
MethodInfo = (MethodInfo)method;
}
///
/// 當前出現位置
///
///
///
///
public MethodInfo MethodInfo { get; private set; }
///
/// 發生異常的 Action
///
public string Action { get; private set; }
///
/// 具體異常
///
public Exception Exception { get; private set; }
}
}
```
### 訂閱事件
訂閱事件,即將其定義為事件的響應者、服務提供者。
當異常發生後,異常的位置,推送異常資訊,那麼誰來處理這些資訊呢?是訂閱者。
這裡我們定義一個異常日誌處理類,來處理程式推送的異常資訊。
在 `AbpBase.Web` 專案的 `Handlers` 目錄中,新增一個 `CustomerExceptionHandler` 類,繼承:
```csharp
public class CustomerExceptionHandler : ILocalEventHandler, ITransientDependency
```
服務要處理事件,必須繼承 ` ILocalEventHandler`,而 `ITransientDependency` 是為了此服務可以可以自動注入到容器中。
其檔案內容如下:
```csharp
using AbpBase.Application.Handlers.HandlerEvents;
using Serilog;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus;
namespace AbpBase.Application.Handlers
{
///
/// 全域性異常記錄日誌
///
public class CustomerExceptionHandler : ILocalEventHandler, ITransientDependency
{
private readonly ILogger _ILogger;
public CustomerExceptionHandler(ILogger logger)
{
_ILogger = logger;
}
public async Task HandleEventAsync(CustomerExceptionEvent eventData)
{
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.AppendLine();
stringBuilder.Append("Action: ");
stringBuilder.AppendLine(eventData.Action);
if (eventData.MethodInfo != null)
{
stringBuilder.Append("Class-Method: ");
stringBuilder.Append(eventData.MethodInfo?.DeclaringType.FullName);
stringBuilder.AppendLine(eventData.MethodInfo?.Name);
}
stringBuilder.Append("Source: ");
stringBuilder.AppendLine(eventData.Exception.Source);
stringBuilder.Append("TargetSite: ");
stringBuilder.AppendLine(eventData.Exception.TargetSite?.ToString());
stringBuilder.Append("InnerException: ");
stringBuilder.AppendLine(eventData.Exception.InnerException?.ToString());
stringBuilder.Append("Message: ");
stringBuilder.AppendLine(eventData.Exception.Message);
stringBuilder.Append("HelpLink: ");
stringBuilder.AppendLine(eventData.Exception.HelpLink);
_ILogger.Fatal(stringBuilder.ToString());
await Task.CompletedTask;
}
}
}
```
這樣寫,記錄的日誌可以有很好的層次結構。
### 釋出事件
定義了事件的格式和定義服務來訂閱事件後,我們來建立一個釋出者。
我們修改一下 `WebGlobalExceptionFilter`。
增加依賴注入:
```csharp
private readonly ILocalEventBus _localEventBus;
public WebGlobalExceptionFilter(ILocalEventBus localEventBus)
{
_localEventBus = localEventBus;
}
```
釋出事件:
```csharp
public async Task OnExceptionAsync(ExceptionContext context)
{
if (!context.ExceptionHandled)
{
await _localEventBus.PublishAsync(new CustomerExceptionEvent(context.Exception,
context.ActionDescriptor?.DisplayName));
...
...
```
### 測試
建立一個 Action :
```csharp
[HttpGet("/T4")]
public string MyWebApi4()
{
int a = 1;
int b = 0;
int c = a / b;
return c.ToString();
}
```
然後訪問 https://localhost:5001/T4 ,會發現請求後報錯
在 `AbpBase.Web` 的 `Logs` 目錄中,開啟 `-Fatal.txt` 檔案。
可以看到:
```
2020-09-16 18:49:27.750 +08:00 [FTL]
Action: ApbBase.HttpApi.Controllers.TestController.MyWebApi4 (ApbBase.HttpApi)
Source: ApbBase.HttpApi
TargetSite: System.String MyWebApi4()
InnerException:
Message: Attempted to divide by zero.
HelpLink:
```
除了異常資訊外,我們還可以很方便的知道異常發生在 `TestController.MyWebApi4` 這個位置。
### 記錄事件
如果在普通方法裡面出現異常,我們這樣這樣記錄:
```csharp
catch (Exception ex)
{
...
new CustomerExceptionEvent(ex, MethodBase.GetCurrentMethod());
...
}
```
`MethodBase.GetCurrentMethod()` 可以獲取當前正在執行的方法,獲得資訊後將此引數傳遞給異常記錄服務,會自動解析出具體是哪個地方發生異常。
由於目前 Web 程式中還沒有編寫什麼服務,因此我們先結合到異常日誌功能中,後面編寫服務時,會再次用到事件匯流排。
完整程式碼參考:https://github.com/whuanle/AbpBaseStruct/tree/master/src/4/AbpBase
下一篇文章地址是 https://www.cnblogs.com/whuanle/p/13061
/// MethodInfo = (MethodInfo)MethodBase.GetCurrentMethod();
///