Code Sense
OO概念
OO
近期在跟高雄軟體King討論Log紀錄的機制方式,得知了AOP的機制,於是認真稍微研究了AOP的使用與實現方式。雖然King提供的AspCore真的快速實現了AOP,不過還是想花點時間去認真看若不使用Framework該如何實現這件事情~
AOP使用橫切入方法在原Class中插入新方法的且不會破壞原Class Method的一種技巧。
將橫切關注點(Cross-cutting concerns)與業務主體進行進一步分離,以提高程式碼的模組化程度!
我們直接來帶例子來瞭解比較快~
如果今天你拿到的他人已寫好Service程式碼如下
突然發現它裡面的使用方法並沒有提供Log機制,此時你想加入Log你該怎麼作?
第一種方法也許你可以直接修改裡面的程式碼
我們直接對Service作修改,注入你自己的Log Fun,並在方法內加入Try-Catch機制,但這種方法很明顯會直接異動到原程式碼。而且基本上Log跟原本的邏輯是沒有直接性的關係,所以寫在一起不是一個很好的方法。
那有何方法可以不用動到裡面的程式碼加入Log呢?使原本Service保有它就是具單純處裡商業邏輯的職責?我們可以使用簡單的靜態Proxy方法去作到這件事情。
直接宣告一個Proxy Class去實作IXXXService方法,因為是透過Proxy,所以有機會在做執行動作的前後做介入(Try-Catch , Log)。這樣可以再不改原程式碼下新增Log Function。
這種概念其實就是一種利用橫切的手法將關注點(Ex:Log)與業務主體進行進一步分離。
進一步來說主要如果有許多程式的需求其實跟程式的邏輯沒有直接關係,但是又需要在適時穿插在Function中,例如最常見的就是在程式中插入紀錄log的功能。像這類的需求通常會使用Aspect或是cross-cutting concern手法。讓此種與邏輯無關的需求與原邏輯Function拆開,提高設計的修改性與降低耦合性。
如下圖示意,一般我們在寫程式時,很常需要處理譬如錯誤紀錄、權限驗證,乃至於額外可能增加使用者查詢歷程等等功能,我們若使用AOP概念,就不用對每個方法依依加入這些功能,而是如灰黃紅的箭頭指向,以橫插方式,所有方法要執行時就一定得經過權限、資料與錯誤的處裡,這就是中介設計的一種概念。
以上講完,如果接手的程式當中,Service有上百支方法,我們不可能逐步透過靜態Proxy去實作對每個方法介入我們要增加的功能,此時我們就會使用動態Proxy來達成這個目標。
在C#實作動態代理可以透過RealProxy與DispatchProxy兩個類別實現。前者可在一般的 .Net Framework上使用,而 .Net Core則需使用後者。
根據MSDN的Dynamic Proxy教學說明情境,我們假設情境上有個Customer Model,我們要透過Repository去操作資料。
因此我們先針對Context設計基礎建設,有Customer Model與Repository相關實作,Repository就作一般的CRUD操作。
在基礎建設實作完後,在一般沒使用Proxy的情境下,可直接Create Repository Instance使用CRUD。
輸出結果
現在我們透過RealProxy實作在Repository層每個CRUD操作介入插入Log以及Try-Catch。
RealProxy基本上實作Dynamic Proxy非常簡單,只需實作Invoke方法。底層原理用C#反射去實作被代理者的方法。有興趣做深層研究可以參考這篇Aspect-oriented programming,有提到AOP的原理大致是根據Reflection, Metaobject Protocols, Composition Filters演變過來的。
實作完動態代理完,Client端使用如下
我們可以在建一個Repository工廠,能彈性產生或組裝不同代理者
透過Dynamic Proxy的使用,在原本的Repository CRUD上加入Log與Try-Catch後,接著我們嘗試建置一個Dynamic Proxy~模擬方法作權限驗證。
實作AuthenticationProxy
實作完AuthenticationProxy後,我們修改一下原先的Repository Factory
Client端使用如下
根據上述範例,我們使用DispatchProxy再實作一次。DispatchProxy操作起來差不多,只是除了要實作Invoke外,對於Create Class Instance那段我們也需要額外實作(Decorate)。
實作完後,Client端使用方法如下
這邊一樣實作RepositoryFactory彈性產生或組裝不同代理者。
Client端使用如下
一樣實作模擬驗證
修改Repository
Client使用不變
目前對於AOP, .Net Core已經有現成的工具可以直接使用,稱AspectCore。針對AspectCore,個人覺得使用上Neil Tsai的AspectCore|.Net Core 輕量AOP實現算解說的蠻清楚的。不過我再根據上述微軟MSDN教學描述的Context作延續使用。
上述我們提到在使用動態Proxy使用Repository撈取Custmoer資料。接下來我們在Web上使用AspectCore去達到這件事情。在Web架構上我們以常見的集中式架構去作設計,撰寫Customer Service取使用Repository。並使用AspCore,在Controller呼叫Service時,增加Log顯示。
我們使用Visual Studio新增一MVC專案,過程沒什麼其他特別設定,這邊起始新增專案就不多加描述。
直接使用Cli安裝AspCore,或使用NutGet套件管理員安裝AspectCore.Extensions.DependencyInjection
接著我們開始實作Service,首先先新增Customer Service Interface,只實作AddCustmoer。
接著撰寫實作
Service撰寫完後,開始用AspCore撰寫攔截器(代理使用Service),攔截Service呼叫並在前後介入Log顯示。
在Starup設置Service與Repository DI設置
在Starup設置DynamicProxy DI設置
AspectCore 也提供 NonAspectAttribute 來使得 Service 或 Method 不會被代理。只要在Interface方法上加上[NonAspect],Service的此方法就會被忽略不被代理
在Program.cs 的 CreateHostBuilder 處加上 UseServiceProviderFactory(new DynamicProxyServiceProviderFactory()),將預設 DI 交由 AspectCore 處理
為了方便Demo看到AspCore攔截Service功能,我們在 .Net Core Web初始專案的HomeController Privacy API加入AddCustmoer功能服務,當User點擊Privacy分頁,就會呼叫CustomerService功能,攔截器會攔截此呼叫,並先印出Log後,在執行Service方法~
此篇大致整理AOP的使用情境與方法,也對於AspCore使用方式簡單實作一個Demo。希望對於未聽過與使用過的人可以快速對於AOP概念有所了解。
Aspect-oriented software development
Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class