iT邦幫忙

DAY 27
8

如何提升系統設計品質 - 技術與工具以.NET為例系列 第 27

[如何提升系統品質-Day27]設計 - Aspect-oriented programming(AOP)

系統品質要好,還有一大部分需仰賴於系統架構的設計。在架構與設計面上,前面幾篇文章提到了『Layer』的觀念,請見:[如何提升系統品質-Day2]重構– UI, Business logic, Data access概念分開。還有提到了『IoC』的設計,讓類別與服務之間,都能透過Interface來溝通與設計,請見:[如何提升系統品質-Day6]重構-簡單使用interface之『你也會IoC』

這篇文章要來介紹另一個設計概念:Aspect-oriented programming(AOP),wiki中文翻成『面向方面的程式設計』,很抽象,不是嗎?這篇文章期望用比較簡單的方式,來說明AOP的設計原理,藉著AOP的設計方式,來降低一些橫切面(垂直層)的程式,散落在各個Layer之間,而造成了重複、關注點混淆的缺點。並以Spring.Net為AOP framework來當範例。

[如何提升系統品質]系列文章連結
前言
設計系統時,大家一定有碰到幾種情況:
1.在執行某些功能前,要加上權限的檢查 (授權稽核)
2.想瞭解某些功能執行的時間花了多久 (效能分析)
3.想知道某個Entity的狀態或是資料面,再經過某些功能後的改變 (軌跡稽核)
4.在執行某些功能前記錄log (用以提供偵錯資訊,以便儘速修復bug)

諸如此類的狀況,請大家回頭想想,這些『功能』,其實與實際類別提供的『服務』並無直接關連。這些功能有很明確的『關注點』,也就是他們自己的職責。但,若沒有AOP的設計方式,這些程式將充斥在系統的各個服務之中,也有可能導致當修改原本服務的方法簽章或其他部分時,上述的那幾種與服務本身無關的功能,會被連帶影響。

所以,這一些功能會被設計在『垂直層』上,而使用AOP的方式,來進行設計。架構圖(資料來源:軟體構築美學)如下:

簡介
在開始介紹Aop之前,我想先介紹一下Proxy Pattern。而Aop的核心部分,就是在Dynamic Proxy的實作。接著我會用個簡單的例子,來說明如何透過Spring.Net來實作,以Aop的方式,來針對橫切面(Aspect)進行執行時間的紀錄。

Proxy Pattern
先來看一下Proxy Pattern的Class diagram:

當Client呼叫ISubject的時候,以Proxy instance injection,而Proxy與RealSubject都有實作ISubject的介面,所以對Client來說,感受不到差異。(IoC)

而Proxy中,會使用到RealSubject,有點像轉播器,不管呼叫到什麼方法,核心都是執行RealSubject的方法,但在方法的前後,以及Proxy的初始化過程,則可以自行加入想要處理的事情。

例如:權限檢核、執行時間記錄、計費、使用次數紀錄、執行前後資料變化(軌跡稽核)等等。

Dynamic Proxy
使用Proxy固然很不錯,但是為了每一個interface,每一個concrete class去增加對應的Proxy class,那也太花功夫了。所以,Spring.Net透過Aop的方式實作了Dynamic Proxy。讓我們能更抽象的關注我們要處理的事情,把Subject被invoke的method抽象出來。動態的去產生proxy object,不用管proxy該怎麼設計,該實作什麼介面,invoke什麼方法,這些都是一樣的動作,所以透過Spring.Net framework,這個功夫就可以省下來。

亮黃色的部分,就是Spring.Net動態產生Proxy的部分。

而紫色的部分,則是4種Advice type,分別是:Before, After, Around跟Throw。Advice的作用,就是當使用Proxy Pattern時加載『處理邏輯』上去的『時間點』。

1.Before是在呼叫原本Subject方法之前。
2.After是在呼叫原本方法之後。
3.Around則是方法前後,將原本被呼叫的Subject.method,抽象成invocation.Proceed()。
4.Throw則是針對方法拋出exception的例外處理。

如此一來,就可以讓Subject專心作Subject應該作的事,Aop的Advice module專心作Advice該作的事。對Client來說,被Interface隔著,根本就沒有感覺差異。透過Aop,將使用的class,被呼叫的class,以及額外加載的class耦合性降到最低。

實作說明
當沒有Aop的時候,呼叫Subject的method方式如下:

當加上Aop之後(這邊的例子是記錄執行時間,所以以around advice為例),就類似替原本的硬碟,加上外接盒一樣,實際運作的還是原本的硬碟,但進出都會通過外接盒。(這也是Aspect橫切面的意義)

接著來看一下實作的程式碼。需求很簡單,呼叫IJoeyService的GetSomething這個方法。然後去看這個方法實際執行花了多少毫秒。

Main:

class Program
   {
       static void Main(string[] args)
       {
           IJoeyService joey = Core.WebUtility.Repository.JoeyService() as IJoeyService;
           string result = joey.GetSomething();
           Console.WriteLine(result);
       }
   }

JoeyService:執行一個loop,從開始StartIndex到EndIndex結束,而這邊的例子,這兩個property是透過Dependency Injection來注入的。

public  class JoeyService: IJoeyService
   {
      public string StartIndex { get; set; }
      public string EndIndex { get; set; }

       public string GetSomething()
       {
           int startIndex = Convert.ToInt32(this.StartIndex);
           int endIndex = Convert.ToInt32(this.EndIndex);

           StringBuilder result = new StringBuilder();
           for (int i = startIndex; i < endIndex; i++)
           {
               result.Append(i.ToString()+",");
           }
           return result.ToString();
       }
   }

接下來重頭戲了,Spring設定檔:Client會呼叫JoeyServiceProxy這個object,parent的部分,則是使用Spring.Net內建的Transaction管理機制(也是透過Aop去加載,在橫切面上進行transaction),而這個JoeyServiceProxy實際的Target為JoeyServiceWithAOP。

再看到JoeyServiceWithAOP這個object,type是Spring.Aop裡面的ProxyFactoryObject,也就是動態產生的Proxy物件,實際代理的Subject,則是Target裡面的JoeyService。而interceptorNames是Spring.Net內建的關鍵字,代表要加載上去的Advice有哪一些object,這邊加載的是performanceLoggingAroundAdvice這個object,而這個object會對應到PerformanceLoggingAroundAdvice.cs。

PerformanceLoggingAroundAdvice.cs:可以看到我們實作了IMethodInterceptor,這個是around advice的interface,Invoke方法中的參數invocation,就是對應到實際的target,而Proceed()就是被呼叫的方法。在呼叫的前後,加上stopwatch來紀錄,實際呼叫這個target的method,究竟花了多少時間。

執行時間結果:

結論
就這麼簡單,如果沒有使用Aop,那想要記錄每個subject的method實際執行時間,勢必不是修改到client的code,就是得修改Subject的code。而且這樣紀錄執行時間的程式,與Client和Subject的邏輯一點關係都沒有,放在Client或Subject的class裡面,就會違反單一職責原則,而且Advice modules的邏輯將散落一地,也不容易達到抽換或加入移除Advice的彈性。

Aop只是個聽起來很抽象、難懂的term,但實際的原理從proxy pattern的角度作切入,就會簡單的多。實作上搭配framework幫忙處理掉很多不必要的瑣碎事務,就能把更多的精神花在該關注的事務上。希望簡單的介紹,可以讓大家很容易就懂,Aop到底是什麼,為什麼需要Aop,以及怎麼透過Spring.Net來實作Aop。

補充:更簡便的AutoProxy
上面例子,說明怎麼在Spring的設定檔,加載Aop到target class上,但當object很多時,難道每一個object都要這樣子多增加一個ProxyFactoryObject來加載Aop模組嗎?想把整個系統的某一個Aop,甚至全部Aop模組功能都取消時,設定檔不就幾乎要重寫了?(別忘了,Aop會作Dynamic Proxy,這中間轉換是需要額外效能的)

這是很實際的需求,例如希望在測試階段,用Aop加載的performance monitor module,針對每一個object的Around advice都要記錄時間。當上線階段,希望performance monitor module全部移除。該怎麼才能讓設定Aop的部分享有彈性,且方便修改呢?

Spring.Net裡面有提供了AutoProxy的物件:Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator。

1.當使用手動設定每一個object的Aop時,如下圖所示:

2.使用AutoProxy後,可以將中間的Spring.Aop.Framework.ProxyFactoryObject設定省略,而由Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator來針對全域的object進行Aop的加載,如下圖所示:

而AutoProxy的設定,該怎麼設定呢?請參考下圖:

只要將type設定成AutoProxy.ObjectNameAutoProxyCreator,而ObjectNames這個property,指的是當spring設定檔中的object id,符合*Service這個命名時,就會加載的Interceptors Aop模組。

類似這樣的AutoProxyCreator,可以設定很多組,針對不同的ObjectNames pattern,去加載不同功用的interceptor。

另外一個最大的好處是,這針對的是Object Names,也就是只針對設定檔上的object id,只要改設定檔,程式不用動,就可以決定有哪些object要加載或卸載Aop模組。


上一篇
[如何提升系統品質-Day26]測試 - 問題單該提供的資訊
下一篇
[如何提升系統品質-Day28]Performance
系列文
如何提升系統設計品質 - 技術與工具以.NET為例30

尚未有邦友留言

立即登入留言