前言

这两周集中时间间歇性读完了《恰如其分的软件架构》这本书。这本书讲的是架构方法,架构方法是一种思维模型(mind set),这种思维模型叫作“风险驱动模型”。

这本书经我们团队的架构师推荐,列在我们团队的集体书目里很久了。但真正去读它、读完它的人又很少。究其原因,还是这本书的内容以谈概念为主,虽然书中举的例子非常生动,仍然始终无法摆脱“为了谈概念而举玩具例子”的问题-这几乎是所有架构书的通病。似乎正统的架构书籍都不可避免地举一些传统行业或者经典软件(比如很多书籍都会反复出现在“xxx 播放器”)的例子。这些软件架构非常经典,可以只用一些小的组件、场景,就讲清楚典型的组件、模式和架构风格的用处。但没有很深的工程/架构经验的读者读这些书的时候,仿佛重新回到了抄书和念书的大学课堂,对于脱离现实的例子只会产生“左耳进右耳出”的感觉。能够温故而知新,是一本书经典化的特征。而能够阅读非入门级的纯理论书籍,则是一个程序员的认知能力和经验达到了一定程度的特征。我读这本书里很多细节还是很痛苦,证明我还是对于形式化的符号(symbol)、记法(notion)还不是很熟悉,而且对于书中运用的问题解析方式、高细节设计具体化的方式,不具有很好的辨析能力。证明我还不是一个已经搭建起来足够成熟的思维框架的架构师。

本文不同于之前我读架构书的总结文,不再是简单地抄书,而是尽量用框架式的方式来重新谈论架构问题,有时候使用书中的论述,有时候使用自己的论述。

另外,这本书的内容很繁复,在现实中应该是给 CMU 的研究生在讲习班上讨论研究的参考教程,可能需要一个学期来专门研究。

架构师的时代背景

这个时代的软件规模和复杂度增长得非常迅速,开发者与软件复杂度的战争也随之升级。我们能够使用的武器从汇编到高级编程语言,从子例程到子对象,但这些东西都不是银弹(没有银弹)。真正能够让我们破解这个“永久的软件工程问题”的,只有化繁为简的思维方式。按照 Fred Brooks 的观点,只有运用各种“武器”,分割系统(1 partitioning),提供知识(2 knowledge),并利用抽象来揭示问题的本质(3 abstract),我们才能应对我们现实中的挑战

接下来本书作者举了一个现实中的例子,“rackspace 公司的日志文件系统”:

rackspace 公司是一间现实中存在的企业,它的数据库经历了本地日志文件-中央数据库(基础的 ETL)-索引簇(使用大数据的 ETL)。

作者观察这个案例,得出了以下结论:

  • 相对于大相径庭的架构,每一代系统的功能大致相同,这证明了正交的设计能够保证功能和业务的延续性。
  • 有以下几种质量属性特别值得关注(这些质量属性接下来还会在下文中反复出现):
    • 可修改性(modifiability)
    • 可伸缩性(scalability)
    • 延迟时间(latency)
  • 我们需要建立概念模型,以帮助推理。这个例子说明了 C/S 和 map-reduce 这两种架构风格在性能上的差别如此之大。如前文中 alan kay 所说,洞察到领域里的问题,是开始推理的关键。
  • 我们不能直接陷入细节里难以自拔,这样会阻碍我们推理问题。比较合适的方法是引入一个具有约束的概念,约束帮我们减少了可能性,这样让我们在解空间里搜索解决方案变得简单。比如本例子中我们使用了 job 来专门描述在系统之间传递、执行的子程序,这样我们可以不必把“小程序是如何在大程序里执行”这种大得没边的问题引入我们的思考

时代在变化,在旧的时代,人们习惯用现成技术(通常就是一门编程语言)里的抽象方式(通常就是这门编程语言里的逻辑单元:类、模块、例程)。在现代,我们要主动做出观念转变(perspective shift),使用架构抽象(architecture abstract)来思考问题,按照本文的观点,要使用组件和连接器来思考问题。

我们历史上已经出现了太多的软件过程(process)了,敏捷过程、瀑布过程和螺旋(spiral)过程,都强调设计。但本书提到了风险驱动模型(risk-driven model),是一种推陈出新的模型,它强调:设计的度,要和风险的度相匹配。接下来作者举了非常多很细节的例子,要理解、认同和记住这些全部细节是不可能的;要对这些观点完全弃之不顾,也无法建立正确的工作方式。一个好的架构师,应该在实战中用最精华的方式,来解决最紧要的问题,而不要导致求全之毁。

这也意味着,我们可能在现实中遇到这样的一些情况:

  • 对于流程很复杂,对功能和质量的要求很 critical,我们可能需要做很严格的复杂设计。我们要求从顶层开始推导我们的系统的全貌,建立领域模型、设计模型和代码模型,借助系统的主模型,把系统的视图导出来,关注单一维度的质量属性。然后我们的系统的每一个子问题足够小,让每一个独立工作的工程师都可以单打独斗解决问题。进行架构提升(architecture-hoisting design),得到一个参考架构(reference architecture)。
  • 只做一些初步的设计,把关键的问题定义好,把解决方案勾勒出来。进行架构无关(architecture-indifferent design)的设计,构建推定架构(presumptive architecture 适用于大多数场景的架构)。

这两种情况都是正常的,都是恰如其分的架构。事实上,大部分的复杂架构只适用于大型项目,而大部分的项目实际上是小项目(现实总是与架构师的雄心壮志相反)。敏捷之所以流行起来,就是因为众多工作者对重型工作流程心怀不满,做出反击。我们不要浪费我们最重要的资源(时间)和次重要的资源(其他人的工作成果)。不要纠结于无法流畅使用的东西,那不是适合我们的解决方案。

大型系统之所以要做恰如其分的架构,是因为如果出现问题,架构重构的风险极大。本身讲的“风险驱动的模型”大体上仍然是一种敏捷过程(所以这是一本关于敏捷架构的书),这种敏捷过程至少让我们关注:

  • 恰如其分的软件架构(just good enough architecture)
  • 概念模型(conceptual model)

这些东西并非敏捷过程所独创,但敏捷过程需要将他们阐发得足够简单易行,让只有一些工程经验的工程师只要读过此书就能看碟下菜,也是一种对于工程学的改进。

市面上有如此多的架构书籍,讲述的架构方法,有的简单,有的复杂,对于自有软件流程的工程师而言,无法分辨哪些方法是高阶的,哪些方法是低阶的,哪些东西是已经过去了的无需学习的,哪些是未来的尚未达到的,真是一个令人苦恼的问题。这也反映了软件工程界百家争鸣,观念混乱。有时候一个读者读完一本书觉得自己能够理解架构和流程,读到第二本书的时候反而发现自己不会了。这本书看待问题的基本观点是“任何一本书,如果只对某种技术只知道一味颂扬,而缺乏批判精神,都是不值得信任的”,值得称道。但这本书高谈阔论的东西对于现实互联网公司的流程而言,又过于理想化,大部分人都无法仔细看待架构问题,所以无法做出很漂亮的架构设计。

何谓软件架构

架构的定义

破这个题非常难。

软件中存在架构,架构决定了软件的基本形态和性质,这是软件工程师的朴素认识,如何导出这种基本基本形态和性质?这就是架构这类学问要探讨的问题了。

有无数本书大谈特谈架构的宏大之处及其种种细节,仿佛架构是一套概念堆积起来的体系大厦。但很少有人能够对谈论的内容进行进行很好的总结,把架构收敛为一个简单的概念。

本书列举了业界关于架构的定义,解释了定义的清晰之处和模糊之处。

清晰的定义包括:

  • 架构这个词实际上是 nasa 发明的。
  • 架构是系统的骨架。
  • 架构与功能(基本上)是正交的-这意味着它们可以单独抽象。这又应了国内某些架构师说的话,架构应该是架子,上面放一些业务。因此架构和功能可以独立升级。
  • CMU 的定义是被普遍接受的:架构是元素和元素之间的一种结构。这衍生出一种新口号(architecture-as-artifact)。
  • Martin Fowler 与 Ralpha Johnson 之间的讨论则告诉我们:架构是项目早期必须做出的一组设计决策(design decision)。

架构的模糊之处在于:
我们无法把架构和详细设计区分出来。有时候我们要做很细很细的设计,但我们仍然是在做架构设计-比如决定某些关键流程里面性能、安全性相关的流程。架构要传达很关键的决策和意图。

所以如果要去面试架构师,要阐述什么是架构,一个折中的答案是:架构是必须早期做出的,关于组件和交互的宏观设计。

架构为什么重要

软件架构太重要了,它影响系统的:

  • 质量属性
  • 功能的实现
  • 约束(约束是用“专制”带来自由的权衡,是导轨 guiding rail)
  • 概念的完整性(这条特别容易被忽略)

架构何时重要

  • 小的解空间。这点很反直觉,在我们没有多少好的选择,只能做差强人意的设计的时候,架构反而特别特别重要。因为这时候我们已经没有质量下降的空间了。
  • 高的失败风险。
  • 难以实现的质量属性。
  • 全新的领域。这时候进行架构设计有助于借助流程帮我们看清楚问题。
  • 产品线。当我们有一类产品时,我们需要通用架构-如,对于保险而言,架构的通用性很重要。

在现实中,金融系统的架构需要特别关注质量属性,因此往往不能采取简单的推定架构,而需要复杂的参考架构。

推定架构与参考架构

推定架构(presumptive architecture)是在特定领域中占据主导地位的架构族。参考架构(reference architecture)描述了针对某一问题在架构层面的解决方案。参考架构意味着着那种雄心勃勃的架构(如 OSI 七层架构),而推定架构意味着(该领域)事实上的标准(如 TCP/IP 五层架构)。

传统的 3-tier 架构至少能够实现了这种封装:

  • 一层处理用户界面。
  • 一层处理业务逻辑。
  • 一层处理数据存储。

事实上,采用推定的 N 层架构的开发者几乎总能做得不错,他们真正的架构决策往往是使用哪些 COTS 现成商业软件,如使用哪种数据库-这也是外包解决方案能够解决一揽子问题的简单解释,这也是很多技术团队能够快速构建业务系统的原因。