中国领先的IT技术网站
|
|

软件项目架构简明进化史

这里,作者将跟我们分享一个局部架构的进化过程。这一演进过程,欢迎大家来讨论和分析。

作者:Virus-BeautyCode来源:博客园|2011-09-01 09:34

沙龙活动 | 去哪儿、陌陌、ThoughtWorks在自动化运维中的实践!10.28不见不散!


1引言

在标题的取名上,不敢说颇费心机,也算得上花费了一点功夫的。首先想到的是“架构设计过程”,又觉得是不是太大了,因为例子比较局部,不是很完整。叫做“结构变化过程”可能更好点。但是又怕名字取的小气了,进来的人少,参与讨论的就更少了,最终还是取了这个有点忽悠人的标题“架构演进”。

今天的这个架构演进,使用系统中一个局部的实例进行推导和演进,一起来观察一下,架构是如何不满足需求的?架构如何演进?更好的架构应该具备哪些条件?有没有更好的呢?

业务场景

图1 业务场景图

从上图可以看出,就是一个电子商务网站常见的支付、支付的后续处理,这样一个业务场景。支持多种支付方式,目前包括银联、支付宝,还有平台账户。平台账户就是注册用户将资金存储在平台为用户建立并维护的一个账户里,购买平台的产品,可以使用平台账户中的资金进行支付。

2业务流程

首先用户选择商品。

下单,进行支付。

选择支付方式。

使用相应支付方式进行支付。第三方支付,会跳转到第三方的支付页面进行支付。

平台进行支付的后续处理,包括成功之后的修改状态等,还包括失败之后的记录标记等。

第三方的支付,在打开第三方支付界面的时候,会告诉它一个平台的回调地址,支付之后,通过回调地址接收第三方支付的结果,然后进行后续处理。使用平台账户支付,就直接进行后续处理就可以了。

当然,这其中还会有一些细节,不在我们的讨论范围。例如:使用平台账户进行支付,判断账户金额是否充足。使用第三方支付,是否记录第三方支付的完整过程,以及完整的支付流程。等等具体的业务细节均不在今天的讨论范围。

3初级架构-用存储过程搞定它

回调地址接收两个参数,一个是订单编号,一个是标志。标志说明是成功还是失败,或者是更加详细的信息。

  1. CREATE PROCEDURE Proc_PaymentHandle  
  2. @OrderSeqNo VARCHAR(36), --订单编号  
  3. @ReturnCode VARCHAR(10), --返回状态码  
  4. @PaymentManner CHAR(1) --支付方式:1银联,2支付宝,3平台账户  
  5. AS  
  6. BEGIN  
  7. IF(@PaymentManner='1')  
  8. BEGIN  
  9. --更新订单状态  
  10. --更新银联支付信息  
  11. RETURN;  
  12. END  
  13. ELSE IF(@PaymentManner='2')  
  14. BEGIN  
  15. --更新订单状态  
  16. --更新支付宝支付信息  
  17. RETURN;  
  18. END  
  19. ELSE IF(@PaymentManner='3')  
  20. BEGIN  
  21. --更新定的状态  
  22. --更新平台账户支付信息  
  23. RETURN;  
  24. END  
  25. END 

配合一段C#代码,判断一下支付方式,然后给存储过程传递参数。这样写的话,上面的这个存储过程很容易就超过1k行了,相信大家也写过1k行以上的存储过程,也维护过这样的存储过程,知道个中的酸甜苦辣。

如果说那一天我们增加了一种支付方式,需要修改的地方包括哪些呢?

界面要修改,存储过程要打开修改,调用的C#代码要修改。真是有点麻烦,最主要的是容易改错了,误改了不应该动的地方才是最要命的。好吧,我们简单分离一下。每种支付方式一个存储过程,把对于支付方式的判断放在代码中,每种支付对应一个代码中的方法。这样需要增加一种的话,只要改改支付方式判断的代码,然后重新写一个存储过程,重新写一个方法调用一下新的存储过程就可以了。可是还有一个问题,更新订单状态好像大家都在做,如果哪一些还需要加一些大家都需要做的事情呢?或者说修改一些大家都需要做的事情的细节?又或者说某两个支付方式需要增加一个处理流程呢?打开存储过程,狂修改吧!!!!

存储过程有几个不便利的地方:

调试不方便

测试不方便

代码不能折叠,多了之后要拖动滚动条才能找得到

逻辑运算、大规模计算是存储过程的弱项

存储过程的优势至少也有一个,就是修改之后,马上可以见到效果。不用编译。

4中级架构-在代码中分离对每种信息的更新

之前的架构代码中有很多的重复地方,例如:对于订单信息的更新。如何把重复降低呢?降低重复也就集中了代码,集中了将来也好维护。而且把它分离出来,独立出来,好像更好点,在需要的地方调用就可以了。如果需要变更订单的更新细节,只要修改一下更新细节就可以了,不需要动支付的代码。减小犯错误的概率。

首先,将各种更新信息独立出来。

  1. public class OrderRepository2  
  2. {  
  3. public void UpdateState()  
  4. throw new System.Exception(); }  
  5. }  
  6. public class PlatformAccountRepository2  
  7. {  
  8. public void Update()  
  9. throw new System.Exception(); }  
  10. }  
  11. public class ZhifubaoRepository2  
  12. {  
  13. public void Update()  
  14. throw new System.Exception(); }  
  15.  }  
  16.  public class YinlianRepository2  
  17. {  
  18. public void Update()  
  19. throw new System.Exception(); }  

使用下面的方法进行支付的后续处理。

  1. public void HandlePaymentResult(PaymentManner2 paymentManner, string orderSeqNo)  
  2. {  
  3. switch (paymentManner)  
  4. {  
  5. case PaymentManner2.PlatformAccount :  
  6. var platformService = new PlatformAccountPaymentResultHandleService2();  
  7. platformService.Handle(orderSeqNo);  
  8. break;  
  9. case PaymentManner2.Yinlian :  
  10. var yinlianService = new YinlianPaymentResultHandleService2();  
  11. yinlianService.Handle(orderSeqNo);  
  12. break;  
  13. case PaymentManner2.Zhifubao :  
  14. var zhifubaoService = new ZhifubaoPaymentResultHandleService2();  
  15. zhifubaoService.Handle(orderSeqNo);  
  16. break;  
  17. }  
  18. public enum PaymentManner2  
  19. {  
  20. Zhifubao,  
  21. Yinlian,  
  22. PlatformAccount  
  23. }  
  24. public class ZhifubaoPaymentResultHandleService2  
  25. {  
  26. private OrderRepository2 _orderManagement;  
  27. private ZhifubaoRepository2 _zhifubaoManagement;  
  28. public void Handle(string orderSeqNo)  
  29. {  
  30. using (TransactionScope scope = new TransactionScope())  
  31. {  
  32. _orderManagement.UpdateState();  
  33. this._zhifubaoManagement.Update();  
  34. scope.Complete();  
  35. }  
  36. }  
  37. }  
  38. public class YinlianPaymentResultHandleService2  
  39. {  
  40. private OrderRepository2 _orderManagement;  
  41. private YinlianRepository2 _yinlianManagement;  
  42. public void Handle(string orderSeqNo)  
  43. {  
  44. using (TransactionScope scope = new TransactionScope())  
  45. {  
  46. this._orderManagement.UpdateState();  
  47. this._yinlianManagement.Update();  
  48. scope.Complete();  
  49. }  
  50. }  
  51. }  
  52. public class PlatformAccountPaymentResultHandleService2  
  53. {  
  54. private OrderRepository2 _orderManagement;  
  55. private PlatformAccountRepository2 _platformAccountManagement;  
  56. public void Handle(string orderSeqNo)  
  57. {  
  58. using (TransactionScope scope = new TransactionScope())  
  59. {  
  60. this._orderManagement.UpdateState();  
  61. this._platformAccountManagement.Update();  
  62. scope.Complete();  
  63. }  
  64. }  

增加支付方式的话,新建一个HandleService类,写一些处理代码,然后在public void HandlePaymentResult(PaymentManner2 paymentManner, string orderSeqNo)方法的switch中增加一个case就可以了。

但是页面的可选支付方式还是写死了,没有动态的变化,支付方式是否可以动态配置呢?而且可以方便的测试呢?例如:虽然我还没有银联的接口,但是我想测试一些,银联支付之后平台的处理是否正确,该更新的信息是否都更新了呢?没有银联的接口,是不是就不能做了呢?有没有办法解决呢?

答案是:有。

还有就是上面的switch。。。case,好像会很长,也很丑,这个地方能否改进呢?很多人在学习了重构之后,会提出很多的方法来解决这个问题,我们再后面也一块来解决一下。

5高级架构-少用存储过程处理业务的灵活架构

我们的高级架构有几个目标

减少存储过程中的业务逻辑,让存储过程更加纯粹的做事,做它擅长的事情。

可以灵活的增加或者减少支付方式。达到在增加或者减少支付方式的时候,尽量少的修改代码,尽量减少依赖。减少支付对于支付方式的依赖,支付方式对于后续处理的依赖。

代码结构更加清晰。

为了达到上面的几个目标,计划独立几个部分。

支付方式的管理。

每一种支付方式的处理过程。这个在中级架构里面已经做的差不多了,这里会做的更好一点,抽象这个支付处理过程。

还有就是要隐藏支付方式和具体的支付方式处理过程映射代码。具体的支付方式指的是:银联或者是支付宝这种具体的一种支付方式。目的就是让对于支付订单的处理独立化,固定化,支持变化。

5.1支付方式的管理

  1. public enum PaymentManner1{  
  2. Zhifubao,  
  3. Yinlian,  
  4. PlatformAccount  
  5. }  
  6. public class PaymentMannerParams  
  7. {  
  8. /// <summary>  
  9. /// 地址还是内部方法  
  10. /// </summary>  
  11. public UriOrFunction UriOrFunction { getset; }  
  12. /// <summary>  
  13. /// 地址  
  14. /// </summary>  
  15. public string Uri { getset; }  
  16. /// <summary>  
  17. /// 方法名  
  18. /// </summary>  
  19. public string FunctionName { getset; }  
  20. enum UriOrFunction  
  21. {  
  22. Uri,  
  23. Function  
  24. }  
  25. }  
  26. public class PaymentMannerManagement1  
  27.  {  
  28. public Dictionary<PaymentManner1, PaymentMannerParams >FindAvailableManner(decimal moneyOfPay)  
  29. {  
  30. throw new System.Exception();  
  31. }  

通过FindAvailableManner方法获取支付方式。每种支付方式PaymentManner,都带有一个参数实体PaymentMannerParams,里面的UriOrFunction来决定是通过网页还是内部方法来支付,Uri就跳转到Uri就可以了,Function就调用FunctionName中的方法就可以了。支付的时候用下面的Pay先获取支付方式信息,然后根据每种支付方式的参数来决定具体的支付。

  1. public class OrderManagement1  
  2. {  
  3. public void Pay(decimal money)  
  4. {  
  5. var manner= new PaymentMannerManagement1().FindAvailableManner(money);  
  6. //后续支付  
  7. }  

之前说的,如果银联还没有接口,或者接口暂时不能用了,想测试一下后续的处理,就可以将银联这种Manner的UriOrFunction设置为Function,现用内部的方法来测试后续的处理是否正确。等可以用的时候,在变更为Uri就可以了。

5.2支付过程的抽象

通过建立支付处理的接口,将支付处理的代码抽象成下面的样子。

  1. public class Service1  
  2. {  
  3. public void HandlePaymentResult(PaymentManner1 paymentManner,string orderSeqNo)  
  4. {  
  5. IPaymentResultHandleService1 handleService = PaymentResultHandleServiceFactory1.GetService(paymentManner);  
  6. handleService.Handle(orderSeqNo);  
  7. }  

这个处理的代码,原则来说以后都不需要修改了。后面要做的就是定义一种新的支付方式枚举量,然后实现IPaymentResultHandleService1 接口,写一些处理的代码就可以了。

5.3完整代码using System;

  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3. using System.Text;  
  4. using System.Transactions;  
  5. namespace ConsoleApplication1  
  6.  {  
  7. public class Service1  
  8. {  
  9. public void HandlePaymentResult(PaymentManner1 paymentManner,string orderSeqNo)  
  10. {  
  11. IPaymentResultHandleService1 handleService = PaymentResultHandleServiceFactory1.GetService(paymentManner);  
  12. handleService.Handle(orderSeqNo);  
  13. }  
  14. }  
  15. public class OrderManagement1  
  16. {  
  17. public void Pay(decimal money)  
  18. {  
  19. var manner= new PaymentMannerManagement1().FindAvailableManner(money);  
  20. //后续支付  
  21. }  
  22. }  
  23. public enum PaymentManner1  
  24. {  
  25. Zhifubao,  
  26. Yinlian,  
  27. PlatformAccount  
  28. }  
  29. public class PaymentMannerParams  
  30. {  
  31. /// <summary>  
  32. /// 地址还是内部方法  
  33. /// </summary>  
  34. public UriOrFunction UriOrFunction { getset; }  
  35. /// <summary>  
  36. /// 地址  
  37. /// </summary>  
  38. public string Uri { getset; }  
  39. /// <summary>  
  40. /// 方法名  
  41. /// </summary>  
  42. public string FunctionName { getset; }  
  43. enum UriOrFunction  
  44. {  
  45. Uri,  
  46. Function  
  47. }  
  48. }  
  49. public class PaymentMannerManagement1  
  50. {  
  51. public Dictionary<PaymentManner1, PaymentMannerParams >FindAvailableManner(decimal moneyOfPay)  
  52. {  
  53. throw new System.Exception();  
  54. }  
  55. }  
  56. public class PaymentResultHandleServiceFactory1  
  57. {  
  58. private static PaymentResultHandleServiceFactory1()  
  59. {  
  60. _serviceMap = new Dictionary<PaymentManner1, IPaymentResultHandleService1>();  
  61. _serviceMap.Add(PaymentManner1.PlatformAccount, new PlatformAccountPaymentResultHandleService1());  
  62. _serviceMap.Add(PaymentManner1.Yinlian, new YinlianPaymentResultHandleService1());  
  63. _serviceMap.Add(PaymentManner1.Zhifubao,new ZhifubaoPaymentResultHandleService1());  
  64. }  
  65. private static Dictionary<PaymentManner1 , IPaymentResultHandleService1> _serviceMap;  
  66. public static IPaymentResultHandleService1 GetService(PaymentManner1 paymentManner )  
  67. {  
  68. return _serviceMap[paymentManner];  
  69. }  
  70. }  
  71. public interface IPaymentResultHandleService1  
  72. {  
  73. void Handle(string orderSeqNo);  
  74. }  
  75. public class ZhifubaoPaymentResultHandleService1:IPaymentResultHandleService1  
  76. {  
  77. private OrderRepository1 _orderManagement;  
  78. private ZhifubaoRepository1 _zhifubaoManagement;  
  79. public void Handle(string orderSeqNo)  
  80. {  
  81. using (TransactionScope scope = new TransactionScope())  
  82. {  
  83. _orderManagement.UpdateState();  
  84.  this._zhifubaoManagement.Update();  
  85.  scope.Complete();  
  86. }  
  87. }  
  88. }  
  89. public class YinlianPaymentResultHandleService1 : IPaymentResultHandleService1  
  90.  {  
  91. private OrderRepository1 _orderManagement;  
  92. private YinlianRepository1 _yinlianManagement;  
  93. public void Handle(string orderSeqNo)  
  94. {  
  95. using (TransactionScope scope = new TransactionScope())  
  96. {  
  97. this._orderManagement.UpdateState();  
  98. this._yinlianManagement.Update();  
  99. scope.Complete();  
  100. }  
  101. }  
  102. }  
  103. public class PlatformAccountPaymentResultHandleService1:IPaymentResultHandleService1  
  104. {  
  105. private OrderRepository1 _orderManagement;  
  106. private PlatformAccountRepository1 _platformAccountManagement;  
  107. public void Handle(string orderSeqNo)  
  108. {  
  109. using (TransactionScope scope = new TransactionScope())  
  110. {  
  111. this._orderManagement.UpdateState();  
  112. this._platformAccountManagement.Update();  
  113. scope.Complete();  
  114. }  
  115. }  
  116. }  
  117. public class OrderRepository1  
  118. {  
  119. public void UpdateState()  
  120. throw new System.Exception(); }  
  121. }  
  122. public class PlatformAccountRepository1  
  123. {  
  124. public void Update()  
  125.  { throw new System.Exception(); }  
  126.  }  
  127. public class ZhifubaoRepository1  
  128. {  
  129.  public void Update()  
  130.  { throw new System.Exception(); }  
  131. }  
  132. public class YinlianRepository1  
  133. {  
  134. public void Update()  
  135. throw new System.Exception(); }  
  136. }  

6总结

类的依赖最好使用抽象,避免具体类的直接引用。

尽量不要再存储过程中处理业务,在系统越做越大,你会越来越赞同我的说法。原因至少两点:1维护累死人,2数据库不擅长数值计算和处理。

职责单一,功能独立,代码分离。

原文链接:http://www.cnblogs.com/virusswb/archive/2011/08/31/2160708.html

【编辑推荐】

  1. 浅析淘宝数据魔方技术架构
  2. 浅析.NET设计架构十条箴言
  3. 揭秘Google+技术架构
  4. 揭秘新版SkyDrive架构的幕后
  5. 97条架构师须知
【责任编辑:彭凡 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

一目了然——Web软件显性设计之路

本书阐述了为什么以及如何设计出简单易用的基于Web的软件,让用户单凭常识即可有效地使用它。主要内容包括:显性设计的概念、如何理解用户...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊
× Python最火的编程语言