2013年10月14日 星期一

MS Enterprise Library 6.0 (四) - Policy Injection Application Block

如果說Unity Application Block可以算得上是一份美味的佳餚,那 Policy Injection Application Block 絕對稱得上是開發人員最華麗的盛宴了。

要介紹Policy Injection Application Block之前,得提醒讀者,建議您先熟悉先前介紹過的Exception Handling Application Block以及Logging Application Block,因為我們待會要利用 Policy Injection Application Block 來作一個應用大集合。

首先,請你回憶一下Exception Handling Application Block,以及想像一下Exception Handling Application Block會怎麼用在你的系統中,以及怎麼幫助你以一個單一的例外處理模塊來管理你開發的系統的Exception...

不要偷懶,想像一下...
...
...
...
...
...

好,接著我們回到現實面,你會發現,我們前面說過,可以透過Exception Handling Application Block來管理例外,提供一個統一的方式來處理各種不同可能的例外...這樣很好,我們的系統透過這樣的設計,已經大幅的將耦合性降低,並且提高了重用性,這樣還有什麽讓人不滿意的地方呢?

有的,當程式碼愈趨鬆耦合時,我們開始看try...catch不太順眼。此話怎說??? 請看下面這塊典型的try...catcah:
public void MethodA()
{
    try
    {
        int a = 10, b = 0;
        a = a / b;
    }
    catch (Exception ex)
    {
       //以Config檔案中的 "Policy" ExceptionPolicy設定來處理
       var rethrow = ExceptionPolicy.HandleException(ex, "Policy");
       //如果設定檔說要重丟例外,就重丟
       if (rethrow) throw;
    }
}


有沒有發現,雖然我們簡化了catch當中的程式碼,但是如果我們仔細思索,會發現try...catch這個機制,幾乎會出現在每一個可能發生例外的method當中,也就是說,如果我們寫的嚴謹一點,try...catch幾乎在每一個重要的method當中都會出現。

(我知道你可以在某些開發環境中透過Global的例外處理模塊來處理,例如ASP.NET的Application_Error,但為了解釋方便,請先當作這個機制不存在....)

也就是說,我們的程式碼可能開始會變成這樣
private void MethodA()
{
    try
    {
        //Business Logic block
        //Business Logic block
        //Business Logic block
    }
    catch (Exception)
    {
        //完全一樣的exception Handling block
    }
}

private void MethodB()
{
    try
    {
        //另一段Business Logic block
        //另一段Business Logic block
        //另一段Business Logic block
    }
    catch (Exception)
    {
        //完全一樣的exception Handling block
    }
}

有沒有發現,每一段Method當中,都有除了Business Logic以外,還有著其他很多其實並非核心的運算邏輯(例如上面的catch區塊...),仔細想想,這樣的情況還非常多,在Method核心的代碼中,總是有一堆跟核心邏輯無關,卻又無法不寫的程式碼,有哪些? 可多了,諸如:權限檢查(permission)、參數檢查(Validation)、例外處理(Exception Handling)、日誌紀錄(Logging)...etc

這些都是撰寫程式碼必須的,但卻跟我們的Business Logic不全然直接相關,這些我們稱為"Infrastructure Logic",在架構上屬於cross-cutting concerns。

這類的程式碼,若能與Business Logic切割開來,能夠立即得到許多好處,諸如:
  1. Infrastructure Logic和Business Logic耦合性降低,兩者分別後更容易重用
  2. 程式碼維護變的更加容易,開發時也可以更專注在Business Logic,不用分心去管雜七雜八的其他程式碼
  3. 程式碼可讀性也變高,閱讀時不會被不相關的code干擾...
  4. ...
總之,似乎好處多多。但由於您可能還沒有辦法理解我們待會要怎麼分割兩者,因此你現在可能完全沒感覺,不急,繼續看下去就會慢慢理解。

=============================================

好,剛才說到,要把程式碼中的"Business Logic"與"Infrastructure Logic"切開來,但是要怎麼切呢? 請回頭看一下上面的那段程式碼:

public void MethodA()
{
    try
    {
        int a = 10, b = 0;
        a = a / b;
    }
    catch (Exception ex)
    {
       //以Config檔案中的 "Policy" ExceptionPolicy設定來處理
       var rethrow = ExceptionPolicy.HandleException(ex, "Policy");
       //如果設定檔說要重丟例外,就重丟
       if (rethrow) throw;
    }
}


基本上,在上面這段程式碼當中,Business Logic只有a=a/b那一塊(雖然只是範例,一點用處都沒有,而且必錯無疑),但為了要預防例外,我們加上了try...catch,而這就是我們所說的 Infrastructure Logic 。

這造成了程式碼之間不必要的耦合性,如果可以切開,那不是挺美好的? 要怎麼切? 有一個美麗的答案,就是... attribute 機制。

想像一下,如果程式碼變成底下這樣呢?
        [ExceptionCallHandler("Policy")]
        public void MethodA()
        {
            int a=10, b = 0;

            a = a / b;
        }
這樣會不會好多了?

我們把原本的try...catch完全拿掉,透過attribute的形式取代,程式碼的效果完全相同,但難看死了的try...catch已經完全消失,開發人員在寫code時,可專注在這段 a=a/b 的Business Logic,是不是清爽很多? 是不是把程式碼中的"Business Logic"與"Infrastructure Logic"切割開來了?

OK,要如何完成這樣的功能? 這就是Policy Injection Application Block的應用"之一"了。

Policy Injection Application Block,讓我們可以在Method上加上attribute(其實還可以透過config檔案來設定,但我們先講attribute,因為我比較喜歡attribute),然後透過attribute來賦予method一個新的功能,來形成Policy Injection。(如果有需要,開發人員也可以自行實作自己的attribute)

==========================================
具體的做法非常簡單,首先,請先在專案中利用NuGet加上幾組Enterprise Library套件:
  1. Policy Injection Application Block
  2. Exception Handling Application Block
  3. Exception Handling Application Block Logging Handler
  4. Logging Application Block
其實本來只需要 1.,但我們要示範加上attribute來處理exception之後,要把exception寫入Log File,所以又加上了2,3,4。

完成後,請撰寫底下程式碼(Console Application):
    class Program
    {
        static void Main(string[] args)
        {
            //抓取Config檔案中的設定,作為configurationSource 
            IConfigurationSource configurationSource = ConfigurationSourceFactory.Create();
            //以Config檔案中的設定,建立logWriterFactory 
            LogWriterFactory logWriterFactory = new LogWriterFactory(configurationSource);
            //以Config檔案中的設定,建立LogWriter
            Logger.SetLogWriter(logWriterFactory.Create());
            //以Config檔案中的設定,建立ExceptionManager
            ExceptionPolicy.SetExceptionManager(new ExceptionPolicyFactory(configurationSource).CreateManager(), true);
            PolicyInjection.SetPolicyInjector(new PolicyInjector(configurationSource));
            //故意寫一段會錯的程式碼
            ClassA ClassA = PolicyInjection.Create< ClassA >();
            ClassA.MethodA();
        }
    }

    class ClassA : MarshalByRefObject
    {
        [ExceptionCallHandler("Policy")] //請注意其中的Policy是個參數
        public void MethodA()
        {
            int a=10, b = 0;

            a = a / b;
        }
    }

注意第20行的ClassA,是繼承自MarshalByRefObject。

回到主程式Main當中的前幾行(5-13),我們先前介紹其他Application Block的時候曾經介紹過,基本上就是透過抓取Config檔案中的設定,來建立PolicyInjector, ExceptionManager, LogWriter...等類別。

比較需要注意的是,15行建立類別ClassA實體(instance)的程式碼被改為透過PolicyInjection來建立,至於後續的用法則完全相同。(也就是說,本來你是直接new一個ClassA,現在改成透過PolicyInjection來建立,這有何差別? (先賣個關子,只能說,裡面用到了Unity Application Block,生成出來的ClassA其實也不是以前那個單純的ClassA了...)

 OK,注意第22行,我們在MethodA上頭加上了Attribute,也就是賦予了MethodA()另一個行為,或許可以看成把Policy注入(Injection)到MethodA身上。如此一來,MethodA()就神奇地有了attribute所描述的功能(什麼功能? 當然就是exception Handling)。

如此一來,你不需要在MethodA()當中撰寫try...catch,而這個method發生例外時,自動會把例外導向到Config設定檔中,參數Policy指向的錯誤處理機制。(如果你不太有印象,回頭看看MS Enterprise Library 6.0 (三) - Exception Handling Application Block 那篇)

請留意,上面這段code要能夠順利執行,除了透過NuGet引用Application Blocks之外,還要設定config檔案,至於如何設定,也請參考MS Enterprise Library 6.0 (三) - Exception Handling Application Block 這篇,因為幾乎一模一樣。

===========================================
到這邊,有瞭解了嗎?

基本上,Policy Injection 讓我們以注入的方式,透過設定檔或是attribute,把特定行為注入(加到)某一個method或某一個object身上,這讓我們前面說到的Infrastructure Logic和Business Logic可以切割開來,砍斷其耦合性,便於個別的重用與維護,也讓開發變得更加簡單。(這種把Business與cross-cutting concerns分隔開來的做法,有個經典的名字,稱作AOP - Aspect-Oriented Programming,有興趣的朋友可以上網找找。)

這麼好? 有沒有代價? 有的...不過既然你不知道,就暫時先不提了。

這個好用的機制,是否有可能客製化呢? 例如我自己發明一兩個attribute,掛上這個attribute,某個Method就自動支援身分驗證檢查? 或是支援count? Log? Validation? Cache? ....

其實都有,上面這些很多在Enterprise Library當中早有現成的,不過也有一些從6.0開始被移除,也有一些你必須自己來繼承ICallHandler實作。

但這些...就等到下次有空再說了...

1 則留言:

小星 提到...

董老師:感謝您的分享,收穫良多,後續的部分一定很精彩,用attribute注入,一直想學習,卻無頭緒