背景
隨著信息化的普及,信息系統越來越多,通常不同系統是采用不同的技術基於不同平台開發的,缺乏統一規劃、統一數據標准、統一調用接口,因此系統之間的交互變得很困難.通常大家在需要一個現有系統提供某方面功能的話就會讓開發人員提供個接口,webservice接口也好,標准http接口也好。然後需求不停的變更,代碼不停的迭代。隨著應用端量的增多,對於類似業務邏輯提供的數據格式,內容的特殊處理,dto的設計等等都在變化。用.Net的同學會了解多一些,用webservice的話,應用端會生成代理類。服務端每次更新對應應用端都要同步更新。至此,我們引出第一個細節問題,解決服務端與應用端的強依賴,設計通用的接口規范。
技術點
1. AOP(切面)
2. JavaScriptSerializer (json序列化)
3. DTO設計
實現
先設計一個通用的接口類
/// <summary> /// 對象處理返回的結果接口 /// </summary> /// <remarks> /// 建議在代碼調用返回值中都采用此類實例為返回值<br /> /// 一般ResultNo小於0表示異常,0表示成功,大於0表示其它一般提示信息 /// </remarks> public interface IAOPResult { /// <summary> /// 返回代碼 /// </summary> int ResultNo { get; } /// <summary> /// 對應的描述信息 /// </summary> string ResultDescription { get; } /// <summary> /// 相應的附加信息 /// </summary> object ResultAttachObject { get; } /// <summary> /// 內部AOPResult /// </summary> IAOPResult InnerAOPResult { get; } /// <summary> /// 處理結果是否成功(ResultNo == 0) /// </summary> bool IsSuccess { get; } /// <summary> /// 處理結果是否失敗(ResultNo != 0 ) /// URL:http://www.bianceng.cn/Programming/csharp/201410/45786.htm /// </summary> bool IsNotSuccess { get; } /// <summary> /// 處理結果是否失敗(ResultNo < 0 ) /// </summary> bool IsFailed { get; } /// <summary> /// 已處理,但有不致命的錯誤(ResultNo > 0) /// </summary> bool IsPassedButFailed { get; } /// <summary> /// 如果處理失敗,則拋出異常 /// </summary> /// <returns>返回本身</returns> IAOPResult ThrowErrorOnFailed(); }
設計一個裡AOPResult實現這個接口,類中添加Serializable屬性表明可序列化,應用到我們的具體的demo如下
[WebMethod(Description = "檢查購物車中的禮品")] [SoapHeader("header")] public AOPResult CheckGiftForCart(List<CartLineDTO> carlist, bool returnflag) { ValidateAuthentication(); return (AOPResult)CartGiftCenter.GiftService.CheckGiftForCart(carlist, returnflag); } [WebMethod(Description = "獲取所有的贈品信息")] [SoapHeader("header")] public AOPResult GetGiftList() { ValidateAuthentication(); var result = (AOPResult)CartGiftCenter.GiftService.GetModelList(); if(result.IsSuccess) { result.ResultAttachObject = result.ResultAttachObject.ObjectToJson(); } return result; } [WebMethod(Description = "根據Id獲得組信息")] [SoapHeader("header")] public AOPResult GetGroupInfoByID(int Id) { ValidateAuthentication(); var result = (AOPResult)CartGiftCenter.GroupService.GetModelInfoById(Id); if (result.IsSuccess) { var resultobj = result.ResultAttachObject as GiftGroup; result.ResultAttachObject = new GroupLineDTO { GroupId = (int)resultobj.GroupId, }.ObjectToJson(); } return result; }
從上面的例子我們可以看出所有的服務方法都采用了相同的返回類型。AOPResult中可以存對象,可以存數組,具體由自己定義。如果返回的是對象我們需要將他序列化成json方式。序列化方法參照下面
public static class JsonExtensions { private static JavaScriptSerializer javaScriptSerializer = new JavaScriptSerializer(); [DebuggerStepThrough] public static string ObjectToJson(this object obj) { return javaScriptSerializer.Serialize(obj); } [DebuggerStepThrough] public static T JsonToObject<T>(this string json) { if (string.IsNullOrEmpty(json)) return default(T); return javaScriptSerializer.Deserialize<T>(json); } }
這麼做的優缺點是什麼,優點是服務協議的雙方有了共同的標准,應用端不再完全依賴於代理,也就是說服務端的變更不會直接影響到應用端。舉個最簡單的例子,應用a先向服務端索取一個對象A,A中包含c屬性,後來b也想服務端索取對象A,但是需要包含d屬性。服務器端更新重新build。按照普通設計a變也需要重新build引用新的服務代理。那麼按照這個設計a便只需要從A中提取c就可以,不管服務端以後如何變更只要c屬性還在,結構為改變就不需要重新build。
應用端簡單示例
var b = CartGiftCenter.GiftService.GetModelList();
string message =b.ResultDescription;
var gift = b.ResultAttachObject as List<GiftDetail>;
另一外的主要的有點就是可以統一的切面編程,aop的概念這裡就不多說了。利用aop的特點我們可以對服務的反饋作統一的操作、檢查。例如系統認證、內容過濾等等。這裡推薦一篇簡單務實的文章 http://blog.csdn.net/yanghua_kobe/article/details/6917228
上文中所附的簡單切面輔助類
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Diagnostics; /// <summary> ///AspectF 的摘要說明 /// </summary> public class AspectF { internal Action<Action> Chain = null; internal Delegate WorkDelegate; [DebuggerStepThrough] public void Do(Action work) { if (this.Chain==null) { work(); } else { this.Chain(work); } } [DebuggerStepThrough] public TReturnType Return<TReturnType>(Func<TReturnType> work) { this.WorkDelegate = work; // URL:http://www.bianceng.cn/Programming/csharp/201410/45786.htm if (this.Chain==null) { return work(); } else { TReturnType returnValue = default(TReturnType); this.Chain(() => { Func<TReturnType> workDelegate = WorkDelegate as Func<TReturnType>; returnValue = workDelegate(); }); return returnValue; } } public static AspectF Define { [DebuggerStepThrough] get { return new AspectF(); } } [DebuggerStepThrough] public AspectF Combine(Action<Action> newAspectDelegate) { if (this.Chain==null) { this.Chain = newAspectDelegate; } else { Action<Action> existingChain = this.Chain; Action<Action> callAnother = (work) => existingChain(() => newAspectDelegate(work)); this.Chain = callAnother; } return this; } public static void Retry(int retryDuration, int retryCount, Action<Exception> errorHandler, Action retryFaild, Action work) { do { try { work(); } catch (Exception ex) { errorHandler(ex); System.Threading.Thread.Sleep(retryDuration); throw; } } while (retryCount-- > 0); } } public static class AspectExtensions { [DebuggerStepThrough] public static void DoNothing() { } [DebuggerStepThrough] public static void DoNothing(params object[] whatever) { } [DebuggerStepThrough] public static AspectF Delay(this AspectF aspect, int milliseconds) { return aspect.Combine((work) => { System.Threading.Thread.Sleep(milliseconds); work(); }); } [DebuggerStepThrough] public static AspectF MustBeNonNull(this AspectF aspect,params object[] args) { return aspect.Combine((work) => { for (int i = 0; i < args.Length; i++) { object arg = args[i]; if (arg==null) { throw new ArgumentException(string.Format("Parameter at index {0} is null", i)); } } work(); }); } public static AspectF MustBeNonDefault<T>(this AspectF aspect, params T[] args) where T : IComparable { return aspect.Combine((work) => { T defaultvalue = default(T); for (int i = 0; i < args.Length; i++) { T arg = args[i]; if (arg==null||arg.Equals(defaultvalue)) { throw new ArgumentException(string.Format("Parameter at index {0} is null", i)); } } work(); }); } public static AspectF WhenTrue(this AspectF aspect, params Func<bool>[] conditions) { return aspect.Combine((work) => { foreach (Func<bool> condition in conditions) { if (!condition()) { return; } } work(); }); } [DebuggerStepThrough] public static AspectF RunAsync(this AspectF aspect, Action completeCallback) { return aspect.Combine((work) => work.BeginInvoke(asyncresult => { work.EndInvoke(asyncresult); completeCallback(); }, null)); } [DebuggerStepThrough] public static AspectF RunAsync(this AspectF aspect) { return aspect.Combine((work) => work.BeginInvoke(asyncresult => { work.EndInvoke(asyncresult); }, null)); } }
缺點:性能問題,首先是對象序列和反序列化的開銷,其次是對象強制轉換的開銷,最後是傳統aop反射的開銷。不過對於一般項目利遠遠大於弊。
再說DTO,這個概念和我們上述的場景完全是相反的論調。了解j2ee的同學對這些術語都很熟悉,Data Transfer Object數據傳輸對象,主要用於遠程調用等需要大量傳輸對象的地方。比如我們一張表有20個字段,那麼對應的持久對象(persistant object,簡稱po)就有20個屬性。但是我們界面上只要顯示10個字段,應用端用webservice來獲取數據,沒有必要把整個PO對象傳遞到應用端,這時我們就可以用只有這10個屬性的DTO來傳遞結果到客戶端。
前文我們介紹AOPResult的時候,不考慮帶寬也不考慮數據的安全策略。實際場景下,DTO的設計也需要考慮很多方面。我自己也不敢說自己設計的就是最合理的,我只分享下我的設計經驗,在數據庫通用(各個系統都能訪問)的前提下,DTO只提供ID等表示字段,或者根據業務場景,如果應用端需要的內容不多可以設計成冗余類型甚至設計成PO。在帶寬充足的場景下例如內網項目,放心的填充吧,沒那麼多性能元素需要考慮。一般場景下,則需要考慮的多些,看方法的使用頻度,公網項目中dto中少存敏感字段等等。
題外
本篇先到此,有疑問或者有其他想法的同學歡迎提出來一起討論,下個章節一起再研究下如何利用泛型最簡單的操作多種類型數據庫。