軟件項(xiàng)目架構(gòu)簡明進(jìn)化史
1引言
在標(biāo)題的取名上,不敢說頗費(fèi)心機(jī),也算得上花費(fèi)了一點(diǎn)功夫的。首先想到的是“架構(gòu)設(shè)計(jì)過程”,又覺得是不是太大了,因?yàn)槔颖容^局部,不是很完整。叫做“結(jié)構(gòu)變化過程”可能更好點(diǎn)。但是又怕名字取的小氣了,進(jìn)來的人少,參與討論的就更少了,最終還是取了這個(gè)有點(diǎn)忽悠人的標(biāo)題“架構(gòu)演進(jìn)”。
今天的這個(gè)架構(gòu)演進(jìn),使用系統(tǒng)中一個(gè)局部的實(shí)例進(jìn)行推導(dǎo)和演進(jìn),一起來觀察一下,架構(gòu)是如何不滿足需求的?架構(gòu)如何演進(jìn)?更好的架構(gòu)應(yīng)該具備哪些條件?有沒有更好的呢?
業(yè)務(wù)場景
圖1 業(yè)務(wù)場景圖
從上圖可以看出,就是一個(gè)電子商務(wù)網(wǎng)站常見的支付、支付的后續(xù)處理,這樣一個(gè)業(yè)務(wù)場景。支持多種支付方式,目前包括銀聯(lián)、支付寶,還有平臺(tái)賬戶。平臺(tái)賬戶就是注冊用戶將資金存儲(chǔ)在平臺(tái)為用戶建立并維護(hù)的一個(gè)賬戶里,購買平臺(tái)的產(chǎn)品,可以使用平臺(tái)賬戶中的資金進(jìn)行支付。
2業(yè)務(wù)流程
首先用戶選擇商品。
下單,進(jìn)行支付。
選擇支付方式。
使用相應(yīng)支付方式進(jìn)行支付。第三方支付,會(huì)跳轉(zhuǎn)到第三方的支付頁面進(jìn)行支付。
平臺(tái)進(jìn)行支付的后續(xù)處理,包括成功之后的修改狀態(tài)等,還包括失敗之后的記錄標(biāo)記等。
第三方的支付,在打開第三方支付界面的時(shí)候,會(huì)告訴它一個(gè)平臺(tái)的回調(diào)地址,支付之后,通過回調(diào)地址接收第三方支付的結(jié)果,然后進(jìn)行后續(xù)處理。使用平臺(tái)賬戶支付,就直接進(jìn)行后續(xù)處理就可以了。
當(dāng)然,這其中還會(huì)有一些細(xì)節(jié),不在我們的討論范圍。例如:使用平臺(tái)賬戶進(jìn)行支付,判斷賬戶金額是否充足。使用第三方支付,是否記錄第三方支付的完整過程,以及完整的支付流程。等等具體的業(yè)務(wù)細(xì)節(jié)均不在今天的討論范圍。
3初級架構(gòu)-用存儲(chǔ)過程搞定它
回調(diào)地址接收兩個(gè)參數(shù),一個(gè)是訂單編號(hào),一個(gè)是標(biāo)志。標(biāo)志說明是成功還是失敗,或者是更加詳細(xì)的信息。
- CREATE PROCEDURE Proc_PaymentHandle
- @OrderSeqNo VARCHAR(36), --訂單編號(hào)
- @ReturnCode VARCHAR(10), --返回狀態(tài)碼
- @PaymentManner CHAR(1) --支付方式:1銀聯(lián),2支付寶,3平臺(tái)賬戶
- AS
- BEGIN
- IF(@PaymentManner='1')
- BEGIN
- --更新訂單狀態(tài)
- --更新銀聯(lián)支付信息
- RETURN;
- END
- ELSE IF(@PaymentManner='2')
- BEGIN
- --更新訂單狀態(tài)
- --更新支付寶支付信息
- RETURN;
- END
- ELSE IF(@PaymentManner='3')
- BEGIN
- --更新定的狀態(tài)
- --更新平臺(tái)賬戶支付信息
- RETURN;
- END
- END
配合一段C#代碼,判斷一下支付方式,然后給存儲(chǔ)過程傳遞參數(shù)。這樣寫的話,上面的這個(gè)存儲(chǔ)過程很容易就超過1k行了,相信大家也寫過1k行以上的存儲(chǔ)過程,也維護(hù)過這樣的存儲(chǔ)過程,知道個(gè)中的酸甜苦辣。
如果說那一天我們增加了一種支付方式,需要修改的地方包括哪些呢?
界面要修改,存儲(chǔ)過程要打開修改,調(diào)用的C#代碼要修改。真是有點(diǎn)麻煩,最主要的是容易改錯(cuò)了,誤改了不應(yīng)該動(dòng)的地方才是最要命的。好吧,我們簡單分離一下。每種支付方式一個(gè)存儲(chǔ)過程,把對于支付方式的判斷放在代碼中,每種支付對應(yīng)一個(gè)代碼中的方法。這樣需要增加一種的話,只要改改支付方式判斷的代碼,然后重新寫一個(gè)存儲(chǔ)過程,重新寫一個(gè)方法調(diào)用一下新的存儲(chǔ)過程就可以了??墒沁€有一個(gè)問題,更新訂單狀態(tài)好像大家都在做,如果哪一些還需要加一些大家都需要做的事情呢?或者說修改一些大家都需要做的事情的細(xì)節(jié)?又或者說某兩個(gè)支付方式需要增加一個(gè)處理流程呢?打開存儲(chǔ)過程,狂修改吧?。。?!
存儲(chǔ)過程有幾個(gè)不便利的地方:
調(diào)試不方便
測試不方便
代碼不能折疊,多了之后要拖動(dòng)滾動(dòng)條才能找得到
邏輯運(yùn)算、大規(guī)模計(jì)算是存儲(chǔ)過程的弱項(xiàng)
存儲(chǔ)過程的優(yōu)勢至少也有一個(gè),就是修改之后,馬上可以見到效果。不用編譯。
4中級架構(gòu)-在代碼中分離對每種信息的更新
之前的架構(gòu)代碼中有很多的重復(fù)地方,例如:對于訂單信息的更新。如何把重復(fù)降低呢?降低重復(fù)也就集中了代碼,集中了將來也好維護(hù)。而且把它分離出來,獨(dú)立出來,好像更好點(diǎn),在需要的地方調(diào)用就可以了。如果需要變更訂單的更新細(xì)節(jié),只要修改一下更新細(xì)節(jié)就可以了,不需要?jiǎng)又Ц兜拇a。減小犯錯(cuò)誤的概率。
首先,將各種更新信息獨(dú)立出來。
- 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(); }
- }
使用下面的方法進(jìn)行支付的后續(xù)處理。
- 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();
- }
- }
- }
增加支付方式的話,新建一個(gè)HandleService類,寫一些處理代碼,然后在public void HandlePaymentResult(PaymentManner2 paymentManner, string orderSeqNo)方法的switch中增加一個(gè)case就可以了。
但是頁面的可選支付方式還是寫死了,沒有動(dòng)態(tài)的變化,支付方式是否可以動(dòng)態(tài)配置呢?而且可以方便的測試呢?例如:雖然我還沒有銀聯(lián)的接口,但是我想測試一些,銀聯(lián)支付之后平臺(tái)的處理是否正確,該更新的信息是否都更新了呢?沒有銀聯(lián)的接口,是不是就不能做了呢?有沒有辦法解決呢?
答案是:有。
還有就是上面的switch。。。case,好像會(huì)很長,也很丑,這個(gè)地方能否改進(jìn)呢?很多人在學(xué)習(xí)了重構(gòu)之后,會(huì)提出很多的方法來解決這個(gè)問題,我們再后面也一塊來解決一下。
5高級架構(gòu)-少用存儲(chǔ)過程處理業(yè)務(wù)的靈活架構(gòu)
我們的高級架構(gòu)有幾個(gè)目標(biāo)
減少存儲(chǔ)過程中的業(yè)務(wù)邏輯,讓存儲(chǔ)過程更加純粹的做事,做它擅長的事情。
可以靈活的增加或者減少支付方式。達(dá)到在增加或者減少支付方式的時(shí)候,盡量少的修改代碼,盡量減少依賴。減少支付對于支付方式的依賴,支付方式對于后續(xù)處理的依賴。
代碼結(jié)構(gòu)更加清晰。
為了達(dá)到上面的幾個(gè)目標(biāo),計(jì)劃獨(dú)立幾個(gè)部分。
支付方式的管理。
每一種支付方式的處理過程。這個(gè)在中級架構(gòu)里面已經(jīng)做的差不多了,這里會(huì)做的更好一點(diǎn),抽象這個(gè)支付處理過程。
還有就是要隱藏支付方式和具體的支付方式處理過程映射代碼。具體的支付方式指的是:銀聯(lián)或者是支付寶這種具體的一種支付方式。目的就是讓對于支付訂單的處理獨(dú)立化,固定化,支持變化。
5.1支付方式的管理
- public enum PaymentManner1{
- Zhifubao,
- Yinlian,
- PlatformAccount
- }
- public class PaymentMannerParams
- {
- /// <summary>
- /// 地址還是內(nèi)部方法
- /// </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,都帶有一個(gè)參數(shù)實(shí)體PaymentMannerParams,里面的UriOrFunction來決定是通過網(wǎng)頁還是內(nèi)部方法來支付,Uri就跳轉(zhuǎn)到Uri就可以了,F(xiàn)unction就調(diào)用FunctionName中的方法就可以了。支付的時(shí)候用下面的Pay先獲取支付方式信息,然后根據(jù)每種支付方式的參數(shù)來決定具體的支付。
- public class OrderManagement1
- {
- public void Pay(decimal money)
- {
- var manner= new PaymentMannerManagement1().FindAvailableManner(money);
- //后續(xù)支付
- }
- }
之前說的,如果銀聯(lián)還沒有接口,或者接口暫時(shí)不能用了,想測試一下后續(xù)的處理,就可以將銀聯(lián)這種Manner的UriOrFunction設(shè)置為Function,現(xiàn)用內(nèi)部的方法來測試后續(xù)的處理是否正確。等可以用的時(shí)候,在變更為Uri就可以了。
5.2支付過程的抽象
通過建立支付處理的接口,將支付處理的代碼抽象成下面的樣子。
- public class Service1
- {
- public void HandlePaymentResult(PaymentManner1 paymentManner,string orderSeqNo)
- {
- IPaymentResultHandleService1 handleService = PaymentResultHandleServiceFactory1.GetService(paymentManner);
- handleService.Handle(orderSeqNo);
- }
- }
這個(gè)處理的代碼,原則來說以后都不需要修改了。后面要做的就是定義一種新的支付方式枚舉量,然后實(shí)現(xiàn)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);
- //后續(xù)支付
- }
- }
- public enum PaymentManner1
- {
- Zhifubao,
- Yinlian,
- PlatformAccount
- }
- public class PaymentMannerParams
- {
- /// <summary>
- /// 地址還是內(nèi)部方法
- /// </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總結(jié)
類的依賴最好使用抽象,避免具體類的直接引用。
盡量不要再存儲(chǔ)過程中處理業(yè)務(wù),在系統(tǒng)越做越大,你會(huì)越來越贊同我的說法。原因至少兩點(diǎn):1維護(hù)累死人,2數(shù)據(jù)庫不擅長數(shù)值計(jì)算和處理。
職責(zé)單一,功能獨(dú)立,代碼分離。
原文鏈接:http://www.cnblogs.com/virusswb/archive/2011/08/31/2160708.html
【編輯推薦】