核心观点

  • 架构就是软件设计本身,没有独立于设计之外的”架构”。
  • 架构是为了解决分工这一永恒难题,但分工无真正的可行解——任何系统都会从低熵演化为高熵。
  • 好架构的三个维度:正确抽象刻画现实领域模型、适应不同协同者的差异、包容所有相关用例。
  • 恰如其分的架构:不追求全大,只应对迫切风险,适可而止;架构范式在实践中慢慢浮现。
  • 警惕技术宏大叙事——没有银弹,只有权衡。

一、架构的定义

架构是一个界定不清的东西,我们很难讲清楚哪些东西是架构,哪些东西不是架构。但软件行业里其实人人都在搞架构,软件设计就是架构本身

架构这个词出现得很早,有些人认为是 NASA(也可能是NATO)发明的。最早的架构定义就是描述软件的结构而已,但现在已经没有多少人谈论他们定义的”软件架构”了。工程师很难克制描述复杂结构的原始冲动,但描述复杂结构的普世标准并不存在。大家常见的各种定义,翻来覆去地重新讲着”软件架构是软件结构的顶层设计或者抽象设计”之类的话。

即使是这种软件架构的定义,也并不为所有人都接受。汗牛充栋的架构书籍里有各种各样的观点,有的进一步把软件架构视作一堆组件和交互的设计,有的则把软件架构视作架构师主观意图的体现。把自己当作架构师的人们,着迷于把软件里的”不变与抽象的部分”和”易变与具体的部分”分离出来,把前者当作架构。

架构师们是如此地热衷于做这样一件事,以至于有些人认为架构设计好了就解决了基本问题,设计不好通常是因为架构不好。于是很多人开始刻舟求剑:从某某颗粒度开始的设计应该叫概要设计,从某某颗粒度开始的设计应该叫详细设计,寻求一个稳定、确切的合理软件架构成为了设计一个软件的先决条件。

为了防止架构出错,架构师们的原始冲动会让他们寻求叠床架屋的解,详细地从问题域推导解空间,然后一层一层堆流程。架构的设计俨然和建筑学里面的蓝图(blueprint)一样,需要一开始就设计得严丝合缝。于是我们在软件工程课本的早期历史部分能见到了瀑布流方法论的失败、OS 360制造了人月神话(Dijkstra 把苏联决定建造 OS360 兼容机称作”美国在冷战中取得的最大胜利”)。硅谷无数的”死亡行军”、“梦断代码”项目为这种流程制造反动,又产生了敏捷运动,SOA 的颗粒度又被进一步缩小,产生了 MSA。如果你在上世纪七十年代参与一项软件工程的架构设计,你要写的架构文档可能长达50页;如果你在上世纪九十年代参与这样的项目,你要写的文档可能会缩减至20页;如果你在当代参与一个中国互联网公司电商系统的项目设计,有的架构师会按照4+1视图构建一些架构基线,有的架构师会写一些兼容 TOGAF 的视图,有的架构师干脆只提供矩形、棱形组成的图。在不同的架构师的视角里,架构到底应该详细到什么程度、应该包含什么要素是无标准可循的

这就产生了一个问题:如果我们不能搞清楚到底什么是架构而什么不是架构,我们只能把”如何评价一个架构的好坏”这个问题,转换为”如何评价一个系统的好坏”。除非我们肯承认,我们应该改变的不是架构设计,而是设计本身

只要稍加观察,所有人都会同意,人类天然就有架构能力。老师们会不厌其烦地教大家,如何拆类,如何拆子类,如何拆函数,如何拆子函数,又如何把这些程序组件连起来,成为一个可以运行的程序。这种 segmentation 的能力就是架构的能力本身,架构就是软件设计本身。当然,这个观点并不新鲜,也并不是我发明的。

从外部视角来看,近些年像《整洁架构》、《软件方法》之类的书里引入了一些从结果定义架构的思路:软件架构是对业务用例的回应,软件架构通过对软件复杂度的控制,为利益相关者(stakeholder)负责

二、架构是为了解无解的问题:分工

架构如何回应业务用例?如何为利益相关者负责?

所有的架构都是为了解决分工问题。正如组织架构是为了解决不同职能的人的分工问题一样,业务架构是为了不同职能的业务模块和流程的分工问题,技术架构是为了解决技术组件的分工问题。绝大部分的技术架构都无法逃脱康威定律的支配。

软件架构对业务用例的回应方式是:让各种业务用例用恰当的模块分工落地。软件架构对利益相关者负责的方式是:让利益相关者在付出最小的使用和维护成本的前提下,得到最高的生产力,创造最多的核心价值

分工是很难解的永恒难题,实际上并无真正的可行解。因为复杂和混乱是无法彻底消除的,而且任何组织自身的发展过程里,总不可避免出现低熵体逐渐演化为高熵体的情况。兵法里讲,兵无常势、水无常形。永远会有新的业务模式出现,也永远会有新的组织模式出现,商业组织里很难有稳定的架构。这产生了这样的常态:再好的架构,也难以避免两类问题:

  • 一方面,因为资源的原因,任何系统一开始就不足以让人心满意足,架构师们总是想着 System V1 有种种缺点,不停歇地构思出一个 System V2,最后失败;
  • 另一方面,任何系统永远要防止从一个”还行的系统”跌落到”屎山”的状态。

大部分互联网公司的新工程师都会指责老架构不堪大用,他们转化为老工程师以后,又会感慨老架构牵一发动全身。因为商业战争的快速变化,在一套系统里,各种野生架构和推定架构并存,不同技术生代的造物被各种飞线黑魔法连在一起。

架构是不能完全解决分工问题的,且它仍然要在这个支配性的枷锁下实现一个个业务用例,它仍然要为利益相关者负责。在此前提下,我们应该接受一个事实:架构应该是一个抱残守缺的东西,我们只能在不好里寻找好。

“There’s no silver bullet, only trade offs.”

三、抱残守缺的好架构应该是怎样的

3.1 如何应对业务复杂度

3.1.1 软件首先是对现实的抽象刻画

在数学里,解是输入的映射:y = f(x)。y 是 x 的产物,重点是找到 f。在工程领域里所有的解都存在于解空间之中,而解空间又必须对问题空间负责。

《人月神话》把软件的复杂度分为必然复杂度和偶然复杂度。作者认为,基本要素的复杂度是不受工程师控制的,也就无法减少,而把它们组合起来的系统的复杂度却受很多组合方法的影响,是受偶然因素影响的。对问题基本要素不恰当的理解,很可能造成不恰当的必然复杂度,也就不可能得到多么低的偶然复杂度。

这个观点看起来是容易理解的,但工程实践里却很难被做到。大部分的业务系统的构建者往往远离第一线的现实场景,通过产品经理来理解现实问题。而对技术团队的现实问题输入,却又凌乱且无序,甚至很多时候并不完整。到底领域里有几个边界、几个模型,每个模型的主子关系如何,模型有多少种状态,状态的跃迁关系应该是怎样的,一起决定了一套系统设计的方方面面。

大家终于逐渐认识到了领域知识才是设计的驱动方,这个主次关系不可调转。要获得对真正的复杂业务的认知,要尊重领域专家的意见。兔子有四条腿,桌子也有四条腿,如果构建一个买卖兔子的系统,能否满足制造桌子的需求呢?很多架构上似是而非的错误,需要到很晚的时期才能被发现,而且绝没有后悔药可吃。

抽象刻画还有另一层引申含义:进行最基础设计(比如建模)时,我们就必须对软件设计作出取舍,软件设计并非对于问题的直观理解的全盘照搬。各师各法容易犯的错误是,在”舍”的问题上比较随意,而在”取”的问题上举棋不定。

面对复杂的领域,有的人只能抽象出五六个模型,在一个流程里对模型进行管理,其他的东西只能通过扩展属性和猴子补丁把系统绑成一个大泥球;有的人能够抽象出25个模型,其中有5个是聚合根,进而推导出若干个领域和限界上下文。前一个设计”丢掉了更多的东西”——舍弃过多的信息量而只简单地搭建起一些”大骨头”的系统缺乏法度,业务一旦进入领域的深度区间,工程师应对复杂性的问题就苦于没有趁手的武器。

有鉴于此,《分析模式》、《四色建模法》、《实现模式》、《企业应用架构模式》等书籍或理论相继问世,专门探讨”如何抽象”、“如何分类”、“如何取舍”。领域驱动设计还专门把这些设计活动冠以战略设计和战术设计这样正儿八经的名字。

架构设计的”取法”也并不是越高大全越好。现代的架构或建模理论浓墨重彩地讲它们的观点时,用到的例子往往是年深日久的传统领域里的项目。而很多新兴领域的业务流程和模型尚未定型,架构师并无多少成法可参考。架构师学习一个领域需要认知迭代的过程,而设计不等人,有时候他们需要在认知迭代的早期就做技术决策。

谨慎的架构师都能宁可留有余地而不做难以转圜的决策,保留决策上的双向门可进可退而绝不走进单向门。把先验的事情和后验的事情区分开来,凡是先验的东西应该积极落地,后验的应该努力探索,在奔跑中用高投入产出比的事情把架构上长长的留白填下去。

3.1.2 软件架构要适应不同协同者的差异

架构是要解决分工的问题的,我们首先要解决不同的工程师的分工问题。这就要求系统本身有多维度的结构——一个系统应该由若干条横线和竖线切分成不同的局部结构。

首先,割线要把领域割开。 现代的商业软件仍然是叠床架屋构建起来的。复杂的系统,除了要做好专业技能的上的水平分层,还要按照模型的维护职责,划定系统间的边界。一个不恰当的架构里,很可能出现不恰当的切割线,使得不同工作者权责利纠缠在一起,产生令人讨厌的藕断丝连。通常,一个完整的架构域经过长期演化,能够保证百分之七十的边界划分清楚,就已经非常不容易。在解这个问题的时候,有些架构师能够妥善应用 MECE 原则,按照金字塔原理对架构进行拆分,设计出来的系统像稳定的大厦或者一棵倒置的大树,就算相当不错。

其次,割线要把不同熟练程度的工程师能够工作的领域区分开。 不同熟练程度的工程师能够在单位工作时间里驾驭的复杂度是有差别的,于是”怎样给他们分配合适的工作”,也成为了架构设计必须考虑的问题。当代的架构师在设计复杂系统架构时,干脆变得非常热衷于拆系统

但过少和过多的系统拆分都可能走火入魔。过少的大服务对于大团队而言是个拥挤的集市,不能支撑敏捷开发;过多的小服务在降低局部复杂度的管理难度的同时,又让全域复杂度呈指数级上升,将很多模块间交互问题转化为系统间交互问题(按照《演进式架构》的观点,架构量子的数量超过可以被手工管理的数量的时候,微小的服务颗粒度就突然变得成本高昂而好处寥寥)。

比较容易被忽视的是,我们还要解决工程师和非工程师的沟通问题。软件是为软件工程师所制造,而又要服务于所有 stakeholder 的多维复合体。想要让”所见与所得”相匹配,并不是要求程序员对其他 stakeholder 的要求照单全收,但一定要有办法让程序的逻辑以一种外行人也能看懂的方式呈现出来。

复杂的结构应该是支持透视的

软件工程领域的大师们很早就发现软件不能使用单一视角描述——软件是横看成岭侧成峰的,于是 UML 在一开始就有 9 种视图。如果一个软件团队使用”4+1”视图构建架构基线,其中的用例和场景脚本描述往往必不可少。好的设计让其他 stakeholder 清楚明白这个系统的每个用例的流程是怎样的,每个用例涉及的模型有哪些,它在生命周期是怎样变化的。

到这里可能有些读者会意识到一个问题:看起来软件大部分时间是写给计算机程序运行的,但软件设计大部分时间是写给人看的

我们还要解决现在的程序员和未来的程序员的沟通问题。很多程序员都承认:一个程序在最初的时候是最好写的,随着维护的深入,它会变得越来越难写。

程序员们很早就搞明白程序的可维护性(maintainability)是一个问题。有些人把可维护性问题当作可读性问题来看,可读性问题的大致定义是:怎样用最简单的语言给未来的人讲故事,让你意图能够正确地传达到后来者的思维里?

  • 第一个常见的解法是写好注释。好的代码应该是自描述的(self-explanatory),代码本身被称作内文档(inner document)。但大部分人不喜欢写注释,也写不好注释,很多遗留代码里的注释甚至是被简单拷贝复制粘贴过来的,只会误导后续的维护者。
  • 第二个常见的解法是重构。对于复杂架构的重写也不是一件轻松的事情,以至于最近很多架构书籍专门发明了一个新词”架构重构”(Architectural Refactoring)。一个可重构的程序一开始就应该是易于重构的,重构的问题又变成一个怎样设计一个可以被重构的架构问题。

有人因此认为,程序的可维护性看起来是一个整洁代码(clean code)问题,它背后还藏着一个整洁架构(clean architecture)问题。在程序开发的古典时代,Unix 程序员是通过把难解的问题的高手解法封装进框架代码本身,只把可扩展部分暴露出来,来简化降低编程的门槛——这正是 Ken Thompson 和 Dennis Ritchie 构建的 Unix 系统可以让架构师和其他维护者跨越时空对话的原因。

Spring、RESTful API 规范和各种 ORM 框架的出现让受困于 EJB 的程序员一下子变得很容易开发出易于维护的企业级程序,因为大部分的规范已经被框架制定好了。convention over configuration 的时代的到来,正是因为有那么多现成的 CRUD 架构和规范可以采用,才产生了那么多的 CRUD 高薪岗位。食髓知味的程序员会在通用框架的基础上追求业务框架,追求”通用能力的沉淀”和”软件要素的复用”,于是产生了 COLA、SOFA 等业务脚手架框架,也产生了各种业务中台、技术中台。架构通过自净化来抵御熵增,把复杂度装在水平线下。

3.1.3 架构要包容所有相关用例

我们很难穷举完我们的业务的用例,我们也不可能一次实现完所有用例,所以我们的架构必须有包容能力,能够面向未来编程。

计算系统的演化恰似我们系统架构的演化。任意一套系统的合格标准之一,就是它能够解决特定领域内的所有问题,哪怕今天这个问题没有出现,等它明日出现的时候,也可以被我们的系统解决。这种架构特性被很多架构理论称为可扩展性(extendability)——“怎样使用最小的维护成本来新增所需功能”

一个可扩展性好的架构,好像一个有无数仓位的架子,当出现新的业务需求的时候,架构师只要把业务拆解好,让自己或其他工程师把业务放进指定仓位即可。可扩展性要求我们的系统有扩展点,简单的可扩展性需求要求我们有简单的扩展点,复杂的可扩展性需求要求我们有层次化的扩展点。面向对象程序设计里控制复杂度的方式是,通过允许继承,让程序组件表现出多态的行为,所以面向对象程序设计在诞生之日起就大行其道。

很多架构师在寻求可扩展性的道路上探索得非常远,从最初的 eclipse 的插件化架构出发,找到了不同的落地路径。阿里出现了 TMF 和星环,美团也出现了 BPF。现在各种中台/平台化架构,已经可以在分布式场景下,让一个系统将另一个系统视为扩展点甚至插件,通过全域的编排能力来交付复杂流程。要找到合适的扩展点,需要对业务核心能力非常强的洞察,懂得在大尺度上做正交分解和组合,无法实现能力和接口的标准化,也就无法实现可编排的架构,稍有不慎就会设计出大而无当的系统。

理想是美好的,现实是骨感的。也有很多的系统架构,总是受到业务剧烈变动的冲击,这产生了这个时代特有的”外部适应性”与长期主义的辩证关系。

不变的东西是事实上存在的,只不过不存在本本主义里而已,亚马逊就提倡把战略设定在恒定不变的东西上。也就是说,10000分的考卷里,有一些知识点是必考的知识点。做架构师要有对领域发展的长期视野和专业判断,专业判断不简单迷信方法论,依赖于实事求是的分析和 trial and error,而且在遇到几字形的反复的时候,要有非常强的定力,在反复中也仍然坚持建设可扩展的架构,努力适应短周期的变化,把变化的东西在长周期装进不变的架构里,达成适应外部和长期获胜的平衡

3.2 软件架构还有很多意想不到的问题要考虑

很多架构师认为,业务复杂度之外还应该考虑技术复杂度,还有非常多的东西是技术复杂度这一范畴不能简单概括的。

很多 system design 面试题都会让设计者”设计一个淘宝”或者”设计一个 Twitter”。这些面试题里还隐藏有其他陷阱:架构者如何识别这个场景下的性能瓶颈和隐式约束。譬如:

  • 一个推特系统每日产生若干张图片,理论上产生的图片数会超越某些存储方案的极限值,你将如何处理?如果引入某个存储方案,如何在解决它的可伸缩性问题的基础上,解决数据可靠性问题?如果你选择了某种共识算法,它将在什么时间界限内满足哪个级别的一致性?
  • 一个推特系统要设计一个开放 API,如何保证它的安全性?你将使用多强的加解密算法或散列算法?你的 API 如何恰当地在区分了资源的边界,支持各种各样的其他系统通过多种协议接入?
  • 这个系统的可管理性如何?可观测性如何?如何衡量它的可演化性,你是否有意识地使用健康度函数和引导变更机制来持续演进它?
  • 如果你所在国的政府禁止你使用某种解决方案,你将如何切换你的系统架构到替代方案上——在切换之前,你的系统是可移植的吗?

通行的架构理论都认为架构应该满足”可用性”、“可靠性”、“可测性”、“高性能”、“一致性”等通用特性,此外还有若干个诸如”管理性”、“可复用性”、“可测试性”之类难以理解的质量属性。我们很难讲一个使用了 Oracle 的某类特性而固若金汤的银行系统不是一个好架构,我们也很难讲一个去掉了 Oracle 从而每年节省了若干成本的架构不是一个好架构。从这个视角来看,架构既是学问,又是实践。

四、适可而止的设计、恰如其分的架构与成败论英雄

到此为止,我们发现架构设计不是在解决一个问题,而是在解决若干个打包在一起的相关问题的集合体。书上的架构理论并不能解决我们的全部问题。因为资源有限,我们很多时候不能推导出全部问题。即使推导出了全部问题,我们也未必能够推导出全部答案。即使推导出了全部的答案,我们也未必能够把答案全部落地(有些答案甚至是相互矛盾的)。

于是乎,现实的世界里,人人都是架构师。学院派架构师设计学院派架构,野生架构师设计各种野生架构。每个团队都有自己独特的问题要解决,做架构设计的时候要相信自己实事求是的判断,而不是盲从别人的架构理论和实践。

在朝生暮死的互联网领域,很多技术架构的存活周期非常短。一个电商团队的架构,可能还没有演进到能够支撑百万量级的用户的阶段,就因为业务被取消而被废弃。在这种场景下追求高大全的架构原则的实践,远不如帮助企业抓住业务机会重要,怎样让架构适应外部变化成为了唯一值得考虑的问题。

这些年新出版的架构理论,已经开始承认这样的现实:“我们不能处理所有问题(更谈不上拿到所有好处),只能应对我们需要迫切解决的风险,做一些适可而止的设计”,这种架构思路被称为”恰如其分的架构”。这种理论认为:

  • 最初人们构建的系统,其实是使用通用架构设计出来的,是”架构无关的设计”。
  • 再往后人们发现了很多特殊的核心能力或者质量属性可以嵌入到设计里,才产生了”提升架构的设计”。
  • 架构范式会在这种流程里慢慢浮现出来,这时候某些早期架构决策变得非常重要了,才出现”专注于架构的设计”,进而出现”参考架构”和”推定架构”。

参考架构 vs 推定架构

参考架构是可能成为事实标准的架构,它尝试对特定领域的架构方案给出一些固定解法,比如一个电商域的架构应当拥有营销域;推定架构是已经成为事实标准的架构,比如一个电商域的架构肯定要有商品域和支付域。

警惕技术流行里的宏大叙事

这个行业前几年动辄”使用中台助力传统企业软件架构转型”,过几年又闹着”要平台化,追求可配置,学 TMF”,这几年又开始大喊”中台已死,DDD 才是未来趋势”,浑然不顾 DDD 是一个早于中台诞生的设计理念。这些理念彼此之间并无高低优劣之分,也绝不是什么魔法咒语。

声称只要使用了某项技术,就能神奇地解决不可解决的问题,只是一种技术迷信。比所有哺乳动物强大得多的霸王龙灭亡在白垩纪,真正在岁月长河中幸存的却是弱小而灵活的哺乳动物。MVC 是一个已经诞生近40年的设计模式,现在仍然是很多企业应用架构的首选架构模式。求全必毁,过犹不及,时间会证明哪些架构方法是合适的。

好的架构能够让各种业务用例用恰当的模块分工落地,让利益相关者在付出最小的使用和维护成本的前提下,得到最高的生产力,创造最多的核心价值。这需要架构的所有利益相关者(用户、维护者)的一致认可,最后我们只能在力所能及的时间周期里,以成败论英雄