应对需求变化的扩展能力,少量修改就可以,无须重构或者重建。
面向对象思想的提出,为了解决可扩展性带来的问题;设计模式,更是将可扩展性做到了极致。两个基本条件:正确预测变化、完美封装变化。
预测变化
不断有新的需求需要实现。架构师试图去预测所有的变化
例如,架构师准备设计一个简单的后台管理系统,当架构师考虑用 MySQL 存储数据时,是否要考虑后续需要用 Oracle 来存储?当架构师设计用 HTTP 做接口协议时,是否要考虑要不要支持 ProtocolBuffer?甚至更离谱一点,架构师是否要考虑 VR 技术对架构的影响从而提前做好可扩展性?不可能每次预测都是准确的,复杂性在于:
不能每个设计点都考虑可扩展性。
不能完全不考虑可扩展性。
所有的预测都存在出错的可能性。
更多是靠自己的经验、直觉,所以架构设计评审的时候经常会出现两个设计师对某个判断争得面红耳赤的情况,原因就在于没有明确标准,不同的人理解和判断有偏差,而最终又只能选择一个判断。
预测很准确,方案不合适,则系统扩展一样很麻烦,对应的两种方案:
一、将“变化”封装在一个“变化层”,不变的部分封装在一个独立的“稳定层”
如果系统需要支持 XML、JSON、ProtocolBuffer 三种接入方式
如果系统需要支持 MySQL、Oracle、DB2 数据库存储
无论采取哪种形式,通过剥离变化层和稳定层的方式应对变化,都会带来两个主要的复杂性相关的问题。
1. 系统需要拆分出变化层和稳定层
对于哪些属于变化层,哪些属于稳定层,不同的人有不同的理解。
2. 需要设计变化层和稳定层之间的接口
对于稳定层来说,接口肯定是越稳定越好;但对于变化层来说,差异的多个实现方式中找出共同点,保证加入新的功能时,原有的接口设计不需要太大修改。例如,MySQL 的 REPLACE INTO 和 Oracle 的 MERGE INTO 语法和功能有一些差异,那存储层如何向稳定层提供数据访问接口呢?是采取 MySQL 的方式,还是采取 Oracle 的方式,还是自适应判断?如果再考虑 DB2 的情况呢?相信你看到这里就已经能够大致体会到接口设计的复杂性了。
二、提炼出一个“抽象层”和一个“实现层”
抽象层是稳定的,实现层可以根据具体业务需要定制开发,当加入新的功能时,只需要增加新的实现,无须修改抽象层。这种方案典型的实践就是设计模式和规则引擎。
以设计模式的“装饰者”模式来分析,下面是装饰者模式的类关系图。
图中的 Component 和 Decorator 就是抽象出来的规则,这个规则包括几部分:
1.Component 和 Decorator 类。
2.Decorator 类继承 Component 类。
3.Decorator 类聚合了 Component 类。
这个规则一旦抽象出来后就固定了,不能轻易修改。例如,把规则 3 去掉,就无法实现装饰者模式的目的了。
装饰者模式相比传统的继承来实现功能,确实灵活很多。例如,《设计模式》中装饰者模式的样例“TextView”类的实现,用了装饰者之后,能够灵活地给 TextView 增加额外更多功能,比如可以增加边框、滚动条、背景图片等,这些功能上的组合不影响规则,只需要按照规则实现即可。但装饰者模式相对普通的类实现模式,明显要复杂多了。本来一个函数或者一个类就能搞定的事情,现在要拆分成多个类,而且多个类之间必须按照装饰者模式来设计和调用。
规则引擎和设计模式类似,都是通过灵活的设计来达到可扩展的目的,但“灵活的设计”本身就是一件复杂的事情,不说别的,光是把 23 种设计模式全部理解和备注,都是一件很困难的事情。
小结
在具体代码中使用过哪些可扩展的技术?最终的效果如何?
评论
在实际工作场景中的解决方案
通过以下技术手段实现良好的可扩展性:(1)使用分布式服务(框架)构建可复用的业务平台。(2)使用分布式消息队列降低业务模块间的耦合性。
(1)分布式服务框架
利用分布式服务框架(如Dubbo)可以将业务逻辑实现和可复用组件服务分离开,通过接口降低子系统或模块间的耦合性。新增功能时,可以通过调用可复用的组件实现自身的业务逻辑,而对现有系统没有任何影响。可复用组件升级变更的时候,可以提供多版本服务对应用实现透明升级,对现有应用不会造成影响。
(2) 分布式消息队列
基于生产者-消费者编程模式,利用分布式消息队列(如RabbitMQ)将用户请求、业务请求作为消息发布者将事件构造成消息发布到消息队列,消息的订阅者作为消费者从消息队列中获取消息进行处理。通过这种方式将消息生产和消息处理分离开来,可以透明地增加新的消息生产者任务或者新的消息消费者任务。
核心就是,封装变化,隔离可变性