架构设计的复杂度来源其实就是架构设计要解决的问题,主要有如下几个:高性能、高可用、可扩展、低成本、安全、规模。复杂度的关键,就是新旧技术之间不是完全的替代关系,有交叉,有各自的特点,所以才需要具体问题具体分析,基于各方考虑设计合适的架构,存在合适的架构,不存在最好的架构。这篇主要讨论可扩展性问题
复杂度来源
可扩展性是指,系统为了应对将来需求变化而提供的一种扩展能力,当有新的需求出现时,系统不需要或者仅需要少量修改就可以支持,无须整个系统重构或者重建
在软件设计领域,面向对象思想、设计模式主要被用于解决可扩展性问题。设计具备良好可扩展性的系统,有两个基本条件:正确预测变化、完美应对变化
预测变化
软件系统在发布后,还可以不断地修改和演进。这就意味着不断有新的需求需要实现。如果新需求能够少改代码甚至不改代码就可以实现。综合分析,预测变化的复杂性在于:
- 不能每个设计点都考虑可扩展性。如果每个点都考虑可扩展性,架构师会不堪重负,架构设计也会异常庞大且最终无法落地
- 不能完全不考虑可扩展性。不能完全不做预测,否则可能系统刚上线,马上来新的需求就需要重构,这同样意味着前期很多投入的工作量也白费了
- 所有的预测都存在出错的可能性。如果预测的事情出错,我们期望中的需求迟迟不来,甚至被明确否定,那么基于预测做的架构设计就没什么作用,投入的工作量也就白费了
针对上边这些复杂性,有个经验性的原则:只预测 2 年内的可能变化,不要试图预测 5 年甚至 10 年后的变化, 变化快的行业,能够预测 2 年已经足够了;而变化慢的行业,本身就变化慢,预测本身的意义不大,预测 5 年和预测 2 年的结果是差不多的。所以2 年法则在大部分场景下都是适用的
应对变化
即使预测的比较准了,但是系统设计的时候如何应对变化的扩展性也不是一个简单的问题,应对变化有两个方案:
1 提炼出变化层和稳定层
这种方案是:将不变的部分封装在一个独立的稳定层,将变化封装在一个变化层(也叫适配层)。这种方案的核心思想是通过变化层来隔离变化
无论是变化层依赖稳定层,还是稳定层依赖变化层都是可以的,需要根据具体业务情况来设计。如果系统需要支持 XML、JSON、ProtocolBuffer 三种接入方式,那么最终的架构就是“形式 1”架构;如果系统需要支持 MySQL、Oracle、DB2 数据库存储,那么最终的架构就变成了“形式 2”的架构了
这种方案的复杂度在于分层的拆分依据,以及各层的接口如何设计
- 明确变化层和稳定层如何拆分:对于哪些属于变化层,哪些属于稳定层需要明确
- 变化层和稳定层之间的接口如何设计:对于稳定层来说,接口肯定是越稳定越好;但对于变化层来说,在有差异的多个实现方式中找出共同点,并且还要保证当加入新的功能时,原有的接口不需要太大修改
3 提炼出抽象层和实现层
提炼出一个“抽象层”和一个“实现层”,因为抽象层的接口是稳定的不变的,我们可以基于抽象层的接口来实现统一的处理规则,而实现层可以根据具体业务需求定制开发不同的实现细节,所以当加入新的功能时,只要遵循处理规则然后修改实现层,增加新的实现细节就可以了,无须修改抽象层。典型的实践就是设计模式和规则引擎。
1 写 2 抄 3 重构原则
不要一开始就考虑复杂的可扩展性应对方法,而是等到第三次遇到类似的实现的时候再来重构,重构的时候采取隔离或者封装的方案
- 1 写:最开始三方支付选择了微信钱包对接,此时不需要考虑太多可扩展性,直接快速对照微信支付的 API 对接即可,因为业务是否能做起来还不确定。
- 2 抄:后来发现业务发展不错,决定要接入支付宝,此时还是可以不考虑可扩展,直接把原来微信支付接入的代码拷贝过来,然后对照支付宝的 API,快速修改上线。
- 3 重构:因为业务发展不错,为了方便更多用户,决定接入银联云闪付,此时就需要考虑重构,参考设计模式的模板方法和策略模式将支付对接的功能进行封装。
开始可以写一些if语句进行分支判断,后续场景多起来后可以考虑设计模式去实现。
总结一下
结合这篇Blog,其实我感觉主要是如何在过度设计与不可扩展间去权衡。长期预测的代价和变数太多,可能在落地前业务就凉了,不做预测又可能刚开始迭代就发现难以支持。所以2年预测是一个经验值,如果到了2年业务发展的好了,会倒逼决策层给资源给钱进行架构升级,如果都用不了2年就凉了那5-10年预测就没意义了;在预测的前提下,我们在方案设计的时候是否可以考虑短、中、长三种方案,短期策略一般考虑的变化少,短视,但迅速,修改小,立竿见影。长期策略一般看重远期,但成本高很高,也很可能预测不中。综合成本情况下如果决定采用短期策略,要考虑如果预测的变化发生了,系统修改为长期策略的代价有多大(想在做前),如果可以演化切换那可以,如果成本很高甚至切换不到长期策略,就需要重新思考了;再说重构,重构应该是一个小步快跑的模式,不要一开始就过度抽象的设计去炫技,而是先实现最基本功能,然后逐步随业务迭代进行变化层隔离。