1引言
在標題的取名上,不敢說頗費心機,也算得上花費了一點功夫的。首先想到的是“架構設計過程”,又覺得是不是太大了,因爲例子比較局部,不是很完整。叫做“結構變化過程”可能更好點。但是又怕名字取的小氣了,進來的人少,參與討論的就更少了,最終還是取了這個有點忽悠人的標題“架構演進”。
今天的這個架構演進,使用系統中一個局部的實例進行推導和演進,一起來觀察一下,架構是如何不滿足需求的?架構如何演進?更好的架構應該具備哪些條件?有沒有更好的呢?
業務場景
從上圖可以看出,就是一個電子商務網站常見的支付、支付的後續處理,這樣一個業務場景。支持多種支付方式,目前包括銀聯、支付寶,還有平臺賬戶。平臺賬戶就是註冊用戶將資金存儲在平臺爲用戶建立並維護的一個賬戶裏,購買平臺的產品,可以使用平臺賬戶中的資金進行支付。
2業務流程
首先用戶選擇商品。 下單,進行支付。 選擇支付方式。 使用相應支付方式進行支付。第三方支付,會跳轉到第三方的支付頁面進行支付。 平臺進行支付的後續處理,包括成功之後的修改狀態等,還包括失敗之後的記錄標記等。第三方的支付,在打開第三方支付界面的時候,會告訴它一個平臺的回調地址,支付之後,通過回調地址接收第三方支付的結果,然後進行後續處理。使用平臺賬戶支付,就直接進行後續處理就可以了。
當然,這其中還會有一些細節,不在我們的討論範圍。例如:使用平臺賬戶進行支付,判斷賬戶金額是否充足。使用第三方支付,是否記錄第三方支付的完整過程,以及完整的支付流程。等等具體的業務細節均不在今天的討論範圍。
3初級架構-用存儲過程搞定它
回調地址接收兩個參數,一個是訂單編號,一個是標誌。標誌說明是成功還是失敗,或者是更加詳細的信息。
CREATE PROCEDURE Proc_PaymentHandle
@OrderSeqNo VARCHAR(36), --訂單編號
@ReturnCode VARCHAR(10), --返回狀態碼
@PaymentManner CHAR(1) --支付方式:1銀聯,2支付寶,3平臺賬戶
AS
BEGIN
IF(@PaymentManner='1')
BEGIN
--更新訂單狀態
--更新銀聯支付信息
RETURN;
END
ELSE IF(@PaymentManner='2')
BEGIN
--更新訂單狀態
--更新支付寶支付信息
RETURN;
END
ELSE IF(@PaymentManner='3')
BEGIN
--更新定的狀態
--更新平臺賬戶支付信息
RETURN;
END
END
配合一段C#代碼,判斷一下支付方式,然後給存儲過程傳遞參數。這樣寫的話,上面的這個存儲過程很容易就超過1k行了,相信大家也寫過1k行以上的存儲過程,也維護過這樣的存儲過程,知道箇中的酸甜苦辣。
如果說那一天我們增加了一種支付方式,需要修改的地方包括哪些呢?
界面要修改,存儲過程要打開修改,調用的C#代碼要修改。真是有點麻煩,最主要的是容易改錯了,誤改了不應該動的地方纔是最要命的。好吧,我們簡單分離一下。每種支付方式一個存儲過程,把對於支付方式的判斷放在代碼中,每種支付對應一個代碼中的方法。這樣需要增加一種的話,只要改改支付方式判斷的代碼,然後重新寫一個存儲過程,重新寫一個方法調用一下新的存儲過程就可以了。可是還有一個問題,更新訂單狀態好像大家都在做,如果哪一些還需要加一些大家都需要做的事情呢?或者說修改一些大家都需要做的事情的細節?又或者說某兩個支付方式需要增加一個處理流程呢?打開存儲過程,狂修改吧!!!!
存儲過程有幾個不便利的地方:
- 調試不方便
- 測試不方便
- 代碼不能摺疊,多了之後要拖動滾動條才能找得到
- 邏輯運算、大規模計算是存儲過程的弱項
存儲過程的優勢至少也有一個,就是修改之後,馬上可以見到效果。不用編譯。
4中級架構-在代碼中分離對每種信息的更新
之前的架構代碼中有很多的重複地方,例如:對於訂單信息的更新。如何把重複降低呢?降低重複也就集中了代碼,集中了將來也好維護。而且把它分離出來,獨立出來,好像更好點,在需要的地方調用就可以了。如果需要變更訂單的更新細節,只要修改一下更新細節就可以了,不需要動支付的代碼。減小犯錯誤的概率。
首先,將各種更新信息獨立出來。
public class OrderRepository2 { public void UpdateState() { throw new System.Exception(); } } public class PlatformAccountRepository2 { public void Update() { throw new System.Exception(); } } public class ZhifubaoRepository2 { public void Update() { throw new System.Exception(); } } public class YinlianRepository2 { public void Update() { throw new System.Exception(); } }
使用下面的方法進行支付的後續處理。
public void HandlePaymentResult(PaymentManner2 paymentManner, string orderSeqNo) { switch (paymentManner) { case PaymentManner2.PlatformAccount : var platformService = new PlatformAccountPaymentResultHandleService2(); platformService.Handle(orderSeqNo); break; case PaymentManner2.Yinlian : var yinlianService = new YinlianPaymentResultHandleService2(); yinlianService.Handle(orderSeqNo); break; case PaymentManner2.Zhifubao : var zhifubaoService = new ZhifubaoPaymentResultHandleService2(); zhifubaoService.Handle(orderSeqNo); break; } }
public enum PaymentManner2 { Zhifubao, Yinlian, PlatformAccount } public class ZhifubaoPaymentResultHandleService2 { private OrderRepository2 _orderManagement; private ZhifubaoRepository2 _zhifubaoManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { _orderManagement.UpdateState(); this._zhifubaoManagement.Update(); scope.Complete(); } } } public class YinlianPaymentResultHandleService2 { private OrderRepository2 _orderManagement; private YinlianRepository2 _yinlianManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { this._orderManagement.UpdateState(); this._yinlianManagement.Update(); scope.Complete(); } } } public class PlatformAccountPaymentResultHandleService2 { private OrderRepository2 _orderManagement; private PlatformAccountRepository2 _platformAccountManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { this._orderManagement.UpdateState(); this._platformAccountManagement.Update(); scope.Complete(); } } }
增加支付方式的話,新建一個HandleService類,寫一些處理代碼,然後在public void HandlePaymentResult(PaymentManner2 paymentManner, string orderSeqNo)方法的switch中增加一個case就可以了。
但是頁面的可選支付方式還是寫死了,沒有動態的變化,支付方式是否可以動態配置呢?而且可以方便的測試呢?例如:雖然我還沒有銀聯的接口,但是我想測試一些,銀聯支付之後平臺的處理是否正確,該更新的信息是否都更新了呢?沒有銀聯的接口,是不是就不能做了呢?有沒有辦法解決呢?
答案是:有。
還有就是上面的switch。。。case,好像會很長,也很醜,這個地方能否改進呢?很多人在學習了重構之後,會提出很多的方法來解決這個問題,我們再後面也一塊來解決一下。
5高級架構-少用存儲過程處理業務的靈活架構
我們的高級架構有幾個目標
- 減少存儲過程中的業務邏輯,讓存儲過程更加純粹的做事,做它擅長的事情。
- 可以靈活的增加或者減少支付方式。達到在增加或者減少支付方式的時候,儘量少的修改代碼,儘量減少依賴。減少支付對於支付方式的依賴,支付方式對於後續處理的依賴。
- 代碼結構更加清晰。
爲了達到上面的幾個目標,計劃獨立幾個部分。
- 支付方式的管理。
- 每一種支付方式的處理過程。這個在中級架構裏面已經做的差不多了,這裏會做的更好一點,抽象這個支付處理過程。
還有就是要隱藏支付方式和具體的支付方式處理過程映射代碼。具體的支付方式指的是:銀聯或者是支付寶這種具體的一種支付方式。目的就是讓對於支付訂單的處理獨立化,固定化,支持變化。
5.1支付方式的管理
public enum PaymentManner1 { Zhifubao, Yinlian, PlatformAccount } public class PaymentMannerParams { /// <summary> /// 地址還是內部方法 /// </summary> public UriOrFunction UriOrFunction { get; set; } /// <summary> /// 地址 /// </summary> public string Uri { get; set; } /// <summary> /// 方法名 /// </summary> public string FunctionName { get; set; } enum UriOrFunction { Uri, Function } } public class PaymentMannerManagement1 { public Dictionary<PaymentManner1, PaymentMannerParams > FindAvailableManner(decimal moneyOfPay) { throw new System.Exception(); } }
通過FindAvailableManner方法獲取支付方式。每種支付方式PaymentManner,都帶有一個參數實體PaymentMannerParams,裏面的UriOrFunction來決定是通過網頁還是內部方法來支付,Uri就跳轉到Uri就可以了,Function就調用FunctionName中的方法就可以了。支付的時候用下面的Pay先獲取支付方式信息,然後根據每種支付方式的參數來決定具體的支付。
public class OrderManagement1 { public void Pay(decimal money) { var manner= new PaymentMannerManagement1().FindAvailableManner(money); //後續支付 } }
之前說的,如果銀聯還沒有接口,或者接口暫時不能用了,想測試一下後續的處理,就可以將銀聯這種Manner的UriOrFunction設置爲Function,現用內部的方法來測試後續的處理是否正確。等可以用的時候,在變更爲Uri就可以了。
5.2支付過程的抽象
通過建立支付處理的接口,將支付處理的代碼抽象成下面的樣子。
public class Service1 { public void HandlePaymentResult(PaymentManner1 paymentManner,string orderSeqNo) { IPaymentResultHandleService1 handleService = PaymentResultHandleServiceFactory1.GetService(paymentManner); handleService.Handle(orderSeqNo); } }
這個處理的代碼,原則來說以後都不需要修改了。後面要做的就是定義一種新的支付方式枚舉量,然後實現IPaymentResultHandleService1 接口,寫一些處理的代碼就可以了。
5.3完整代碼
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Transactions; namespace ConsoleApplication1 { public class Service1 { public void HandlePaymentResult(PaymentManner1 paymentManner,string orderSeqNo) { IPaymentResultHandleService1 handleService = PaymentResultHandleServiceFactory1.GetService(paymentManner); handleService.Handle(orderSeqNo); } } public class OrderManagement1 { public void Pay(decimal money) { var manner= new PaymentMannerManagement1().FindAvailableManner(money); //後續支付 } } public enum PaymentManner1 { Zhifubao, Yinlian, PlatformAccount } public class PaymentMannerParams { /// <summary> /// 地址還是內部方法 /// </summary> public UriOrFunction UriOrFunction { get; set; } /// <summary> /// 地址 /// </summary> public string Uri { get; set; } /// <summary> /// 方法名 /// </summary> public string FunctionName { get; set; } enum UriOrFunction { Uri, Function } } public class PaymentMannerManagement1 { public Dictionary<PaymentManner1, PaymentMannerParams > FindAvailableManner(decimal moneyOfPay) { throw new System.Exception(); } } public class PaymentResultHandleServiceFactory1 { private static PaymentResultHandleServiceFactory1() { _serviceMap = new Dictionary<PaymentManner1, IPaymentResultHandleService1>(); _serviceMap.Add(PaymentManner1.PlatformAccount, new PlatformAccountPaymentResultHandleService1()); _serviceMap.Add(PaymentManner1.Yinlian, new YinlianPaymentResultHandleService1()); _serviceMap.Add(PaymentManner1.Zhifubao,new ZhifubaoPaymentResultHandleService1()); } private static Dictionary<PaymentManner1 , IPaymentResultHandleService1> _serviceMap; public static IPaymentResultHandleService1 GetService(PaymentManner1 paymentManner ) { return _serviceMap[paymentManner]; } } public interface IPaymentResultHandleService1 { void Handle(string orderSeqNo); } public class ZhifubaoPaymentResultHandleService1:IPaymentResultHandleService1 { private OrderRepository1 _orderManagement; private ZhifubaoRepository1 _zhifubaoManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { _orderManagement.UpdateState(); this._zhifubaoManagement.Update(); scope.Complete(); } } } public class YinlianPaymentResultHandleService1 : IPaymentResultHandleService1 { private OrderRepository1 _orderManagement; private YinlianRepository1 _yinlianManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { this._orderManagement.UpdateState(); this._yinlianManagement.Update(); scope.Complete(); } } } public class PlatformAccountPaymentResultHandleService1:IPaymentResultHandleService1 { private OrderRepository1 _orderManagement; private PlatformAccountRepository1 _platformAccountManagement; public void Handle(string orderSeqNo) { using (TransactionScope scope = new TransactionScope()) { this._orderManagement.UpdateState(); this._platformAccountManagement.Update(); scope.Complete(); } } } public class OrderRepository1 { public void UpdateState() { throw new System.Exception(); } } public class PlatformAccountRepository1 { public void Update() { throw new System.Exception(); } } public class ZhifubaoRepository1 { public void Update() { throw new System.Exception(); } } public class YinlianRepository1 { public void Update() { throw new System.Exception(); } } }
6總結
類的依賴最好使用抽象,避免具體類的直接引用。
儘量不要再存儲過程中處理業務,隨着系統越做越大,你會越來越贊同我的說法。原因至少兩點:1維護累死人,2數據庫不擅長數值計算和處理。
職責單一,功能獨立,代碼分離。