在14,15年間帶領幾個不同的團隊,交付了幾個項目,在這個過程中,雖然幾個項目的業務不一樣,但是很多應用程式架構基礎性的功能卻是大同小異,例如認證、授權、請求驗證、異常處理、DTO、日誌、審計、定時任務、調度、多語言、應用配置管理等等這些功能。但是由於項目受限於進度、資源、團隊成員的背景,在當時卻難 ...
在14,15年間帶領幾個不同的團隊,交付了幾個項目,在這個過程中,雖然幾個項目的業務不一樣,但是很多應用程式架構基礎性的功能卻是大同小異,例如認證、授權、請求驗證、異常處理、DTO、日誌、審計、定時任務、調度、多語言、應用配置管理等等這些功能。但是由於項目受限於進度、資源、團隊成員的背景,在當時卻難於做到各個項目的統一,只能用拷貝的方式,然後在不通的項目中各自再根據各自的需求去做改進。這促使我下定決心去整理實現一個通用的應用程式級別的框架,來提升項目交付的效率和質量。
在整理這個框架的過程中,參考了一些開源框架的設計和實現,無意中發現了ABP(ASP.NET Boilerplate)已經實現的正是我想要的,本著不重覆造輪子的原則,在對ABP做了POC和評估後,在向整個評審小組展示時,儘管有諸多細節大家意見不盡相同,但對於整體框架卻是少有的一致好評,在後來的項目交付中使用ABP也就是順利成章的事了。當時ABP的版本還是0.5(現在的最新版本是3.5),儘管也踩了一些坑,但是總的來說還是大幅的提高了項目交付效率。
好了,廢話不多說,我們進入正題。
什麼是ABP
ABP(ASP.NET Boilerplate)是一個開源的應用程式框架,以幫助開發人員快速開發。但它又不僅僅是一個框架,更提供了一套基於DDD的架構模型和最佳實踐。
快速示例
下麵我們來研究一個最簡單的示例來看看使用ABP好哪些好處
public class TaskAppService : ApplicationService, ITaskAppService
{
private readonly IRepository<Task> _taskRepository;
public TaskAppService(IRepository<Task> taskRepository)
{
_taskRepository = taskRepository;
}
[AbpAuthorize(MyPermissions.UpdateTasks)]
public async Task UpdateTask(UpdateTaskInput input)
{
Logger.Info("Updating a task for input: " + input);
var task = await _taskRepository.FirstOrDefaultAsync(input.TaskId);
if (task == null)
{
throw new UserFriendlyException(L("CouldNotFindTheTaskMessage"));
}
input.MapTo(task);
}
}
這裡我們看到的是一個Application Service類, TaskAppService, 裡面定義了一個方法UpdateTask. Application Service在DDD的設計中是直接被展示層所調用的,簡單來說,一個前端頁面可以直接調用TaskAppService.UpdateTask.
就這個簡單的示例,我們一起來看看使用ABP有哪些好處。
- 依賴註入 - ABP提供了一個慣用的DI基礎框架,所謂慣用,就是大家平常使用的DI方式一致,保持大家的使用習慣。因為這個示例是在應用服務層,所以註入容器中的實例生命周期都是短時的(每個請求創建一次,生命周期與請求相同)。 它可以簡單方便的註入任何依賴,比如在本示例中的IRepository
- 倉儲 - ABP可以為每一個實體都創建一個預設倉儲,在示例中是IRepository
- 授權- ABP可以使用聲明式的方式來檢查許可權。在示例中,如果一個用戶沒有登錄,或者沒有“UpdateTasks”的許可權,那麼他將不能訪問UpdateTask方法。 ABP不單單使用聲明式的特性來檢查許可權,它還提供了其他的授權方式
- 請求驗證- ABP自動的檢查請求輸入(input)是否為null, 並且可以基於標準的數據註解和自定義驗證規則來檢查輸入中的屬性是否合法。如果請求不合法,它將會拋出一個驗證異常。
- 審計日誌- ABP會基於慣例和配置,自動為每一個請求記錄訪問的用戶、瀏覽器、IP地址、調用的服務、方法、參數、調用時間、耗時、和其它一些信息。
- 工作單元- 在ABP中,每個應用服務方法,都被預設視為一個工作單元. 在進入方法時,ABP會自動的打開連接並開啟事務,如果方法在執行過程沒有任何異常,並且成功完成,那麼在退出方法時,ABP會自動提交事務並釋放連接。不管方法中使用了一個還是多個倉儲,他們都是原子的,在一個事務中,所有的實體改變都會在事務提交時自動保存。正如示例中所示,我們甚至都不用調用顯示的 '_repository.Update(task)'方法來保存數據更新。 不過我個人建議儘管可以不顯示調用更新,但是從代碼的可讀性和可維護性還是顯示的調用'_repository.Update(task)'方法
- 異常處理- 在ABP我們幾乎不用手動的來處理異常,ABP會預設自動處理所有異常。如果有異常發生,ABP會自動的記錄它,並返回合適的結果給客戶端。例如,如果這是一個AJAX請求,它會返回一個JSON對象給客戶端,並指明有一個錯誤發生。它會向客戶端隱藏真實的異常,除非我們使用UserFriendlyException.
- 日誌- 我們可以使用基類中定義的Logger對象來寫日誌。 ABP預設使用Log4Net來寫日誌,當然我們也可以通過修改配置來使用其他的日誌框架。
- 本地化(多語言)- 在示例中,當拋出異常時,使用了"L"方法,它會根據用戶文化配置自動進行本地化處理。
- 自動映射- 在示例的最後一行,我們使用了ABP的MapTo擴展方法來講輸入對象的屬性映射到實體對象的屬性。它使用了AutoMapper庫來執行映射,我們可以很容易的基於命名約定(簡單來講就是屬性名相同,當然也可以指定)來將一個對象的屬性來映射到另一個對象的屬性。通常不同層都會定義自己的數據對象模型,而在層與層之間進行數據交換時,就設計到不同數據對象的轉換,這個時候就是AutoMapper大顯身手的好時機。
- 動態API層- TaskAppService只是一個一般的類,通常我們需要寫一個Web API Controller包裝器來將TaskAppService的方法以API的形式暴露給客戶端調用,但是ABP在運行時已經自動為AppService的方法生成了API介面,所以這樣看起來,就像是客戶端直接調用了AppService的方法(但實際不是)。
- 動態Javascript AJAX代理- ABP在前端為應用服務的調用創建了代理方法,這樣就可以在前端像調用Javascript方法一樣調用應用服務。
在示例中,我們可以看到使用ABP的優勢,通常如果我們來做這些事情,會花費大量的時間,但是ABP框架都自動的為我們處理了。這裡必須點個贊了。
此外,除了這個示例中展示的ABP的優勢以外,ABP還提高了一個健壯的基礎架構和應用模型。包括模塊化、多租戶、緩存、配置管理、調度和後臺任務、數據過濾、領域時間、單元測試和集成測試等等。它讓我們可以集中關註在業務實現上,而不用重覆的去造輪子。