前言
为什么要首先谈扩展性呢?其实是因为笔者从事软件行业长久以来,最烦躁的,也是经常听到身边同事抱怨的一句话就是:我靠!天啦撸!需求又变啦!!!
其实需求变更就涉及到系统架构的特性(关注点)之扩展性。系统扩展性高,那么任你需求如何变更,我自岿然不动!就像九阳真经中所说的“他强由他强,清风拂山冈。他横任他横,明月照大江。”反之,微末变更也会让我哭爹喊娘,埋头做码畜!
既然要谈扩展性,那么我首先来尝试定义下这个特性(非官方定义,秉承一个主旨:将所学所想用自己的语言表述出来)。
定义
系统提供的资源(能力或服务)能够在有限的变化内支持尽可能多的潜在业务场景的能力。
有限
有限说明不是不能变化,而是变化很小,或者说变化的代价很小。控制变化本身也是有代价的,如果真要控制到无须任何变化可以支持近似于所有的业务场景,那需要付出的代价是很大的。道理很简单,任何一件事要做到极致都是极其困难的一样。
变化
变化就是衡量的主体,主要包括代码变化和配置变化;而代码变化又可以简单分为新增代码和修改代码。如果你了解软件设计思想之配置优先原则和约定优先原则,以及软件设计原则之开-闭原则,那么就知道以上不同的变化实际会产生不同的变化代价。配置变化的代价小于代码变化,新增代码的代价小于修改代码!
尽可能多
说明并不是要满足所有的潜在业务场景,而是在一定的限度内,这样才能更有效的控制成本。就好像我们做软件设计时,首先需要确定清晰的问题域,确定问题域的目的就是明确系统边界,即:
1. 当下需要支持哪些特性?
2. 当下不支持哪些特性?
3.以后可能支持哪些特性?
4.以后不支持哪些特性?
潜在
潜在意味着不是指已经支持的业务场景,而是那些将来可能会支持的业务场景,或者无法预料的业务场景。
实例一:某客户关系管理系统(CRM)需要提供API给第三方系统
这个场景涉及到API的设计。API就是提供给第三方使用系统某种资源(服务或能力)的。那么如果我们不考虑对其他架构决策特性的影响,单纯提升API的扩展性,我们只需要牢牢把握住一点:基于业务分析,提供尽可能完善的细粒度的原子服务API。这其实也是软件设计原则之 -单一职责原则的核心思想!这样即使有新的业务场景需要支持,绝大部分情况下消费方都可以通过组合API的方式解决问题,当然这一定程度上增加了消费方使用API的难度,也就是降低了API的易用性。
考虑下一个CRM系统,其主要就是管理客户资料的,那么其实体信息一般包括客户信息、用户信息、账户信息、地址信息、客户密码信息…等等。按照以上尽量提供原子服务的思路,我们应该提供根据ID(或者关键字段)查询每一种实体信息的接口,而不是只提供一些组合接口,比如根据客户ID查询客户信息和地址信息的接口。这样将来需要提供一个只提供客户信息场景时,我们就需要重新开发提供一个接口。
当然,读者可能也会说,那我提供组合接口还方便了消费方呢,如果只提供原子接口,消费方要同时获取客户信息和地址信息时,需要调用两次API呢!没错,所以之前笔者强调了“单纯提升API的扩展性”这个前提,而且消费方的便利与否,其实是后续文章会介绍的易用性范围了。
实例二:某消息中间件需要支持多种通信协议
这个场景很常见,任何一个系统的接入层都希望能够支持多种通信协议,以满足不同的第三方系统接入需求。那么碰到这种场景,我是不是要把所有常见的通信协议:HTTP、TCP、SOAP、FTP…等等全部实现呢?那么大家思考一下就知道不会这么选择了!为什么呢?且不说协议种类本身很多,单说实现支持每一种常见协议,其代价都不是1+1这么简单的咯!
因为协议层是处于软件底层,所以其变动代价都是极其大的!我们要记住:越是底层的设计,变化的代价越大!因此,保证通信协议的扩展性对系统设计而且,尤其的有价值!
那么在这种场景下,我们如何提升扩展性呢?其实我们学习面向对象编程的第一天就知道这个概念:抽象!我们抽象出一层协议适配层API,屏蔽上层对底层协议的访问细节,也就是上层依赖于底层的抽象,而不是底层的具体实现!也是设计模式之适配器模式的核心思想。而且上层依赖底层抽象,实际上是延迟了对于具体实现的决策,对于动态编程也是由很大意义的!
当我们抽象出一层之后,那么上层访问底层只需要使用同一套抽象层API即可,至于底层具体使用什么协议其实是不关心的。这样,当我们想增加一个新协议时,代价就很小,而且基本对上层透明无影响。最终这样的设计会极大的提升系统的扩展性!