protected ApiProxyBase(IRestClient restClient) { Client = restClient; } protected IRestClient Client { get; private set; } protected ILog Log = LogManager.GetLogger("Request");Common.Logging則是直接在base class使用,並未採用constructor injection,主要因為這麼做不會對unit test造成太大影響,目前團隊也未對log機制來做unit test。log的內容則是hardcode在這支base class裡,所以當log的內容需要修改時,這支base class就得修改重新發佈新版本。log的部份主要是在發送HTTP request和接收到HTTP response時使用,如
protected IRestResponse<T> GetRestResponse<T>(IRestRequest restRequest) { var sb = new StringBuilder(); try { AppendRequestLog(restRequest, sb); IRestResponse<T> response = Client.Execute<T>(restRequest); AppendResponseLog(response, sb); Log.Info(sb); return response; } catch (Exception ex) { Log.Error(sb, ex); throw new BaseClassException("Failed to get information.", ex); } }現行的log設計有幾個主要問題
- log內容若修改,base class就得修改重新發佈新版本函式庫
- log機制侷限於使用Common.Logging,函式庫打包時得多加入Common.Logging
- 函式庫使用者相依於Common.Logging,且需使用特定Common.Logging adapter,如log4net/NLog,或是自行實作出一個adapter
- log內容無法由函式庫使用者來自訂擴充或修改
文章開始所提到的IRestClient介面目前是透過IoC container經由constructor injection把實體類別帶入base class裡。目前團隊使用的IoC container為Unity,如果Unity提供AOP的機制讓我們在具現化RestClient類別時,把log邏輯塞到Execute<T>方法(signature為public virtual IRestResponse
目前參考base class函式庫的專案使用的Unity nuget版本為3.0.1304,可由https://www.nuget.org/packages/Unity/3.0.1304安裝。Unity本身預設沒有支援AOP的機制,需額外安裝Unity Interception Extension。安裝成功的話,專案會多出兩個參考,Microsoft.Practices.Unity.Interception和Microsoft.Practices.Unity.Interception.Configuration。
接下來在宣告container時把這個extension加進來
var container = new UnityContainer(); container.AddNewExtension<Interception>();並建立一類別實作IInterceptionBehavior介面,如
internal class LoggingInterceptionBehavior : IInterceptionBehavior { protected ILog Log = LogManager.GetLogger("Base"); public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) { if (input.MethodBase.Name != "Execute") { return getNext()(input, getNext); } var sb = new StringBuilder(); try { var request = input.Arguments["request"] as IRestRequest; if (request == null) { return getNext()(input, getNext); } AppendRequestLog(request, sb); IMethodReturn result = getNext()(input, getNext); var response = result.ReturnValue as IRestResponse; AppendResponseLog(response, sb); Log.Info(sb); return result; } catch (Exception ex) { Log.Error(sb, ex); throw; } } private void AppendRequestLog(IRestRequest restRequest, StringBuilder sb) { // Log request } private void AppendResponseLog(IRestResponse response, StringBuilder sb) { // Log response } public IEnumerable<Type> GetRequiredInterfaces() { return Type.EmptyTypes; } public bool WillExecute { get { return true; } } }最後用Unity具現化RestClient
container.RegisterType<IRestClient, RestClient>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<LoggingInterceptionBehavior>());到這裡我們已經把log邏輯從base class內拉出來,接下來便可以把base class裡原本使用到的log邏輯移除,base class顯得乾淨許多,函式庫也不用再相依Common.Logging。日後如果要更換logging framework或是採用自己撰寫的log元件,只需要修改LoggingInterceptionBehavior這支類別即可。