3.1 架构基础
概述与动机
软件架构是一系列重大设计决策的集合,这些决策一旦做出,改动成本就很高:包括主要组件的结构、组件之间的关系,以及整个系统必须体现的各种属性。可以把它看作是让众多人协同构建出一个连贯产品的共享心智模型。在小团队中,架构可以存在于几个人的脑海中,并随着开发不断演进。而在一个大型组织中(数百名工程师、数十个团队、多个产品线、跨年度的路线图),架构就成了让所有人保持协调一致的关键所在。当架构清晰时,各团队可以独立行动而不相互冲突;当架构含混不清时,每一次跨团队依赖都会演变成一场谈判,每一次事件都会变成一场考古发掘。
对企业和政府而言,架构基础尤为重要,因为这些系统生命周期长、监管严格,并且跨多个部门共用。税务系统、福利平台、全国医疗档案系统,或银行的核心账本系统,其寿命往往会超过建造它们的人的职业生涯。你今天在耦合度、数据所有权和质量属性方面做出的决策,将在此后十年甚至更长时间里限制着可能性的边界。监管机构和审计人员越来越期望看到有文档记录、经得起推敲的架构:证明可靠性、安全性、隐私性和无障碍性是被设计进去的,而不是事后附加上去的。把架构基础做对,绝非纸上谈兵()它决定了一个平台是能够适应新的强制要求,还是不得不从头重建。
本章讨论那些比技术潮流更为持久的架构基础:质量属性(各种“性”)、架构显著需求、适应度函数与演进式架构、使用 C4 和 arc42 进行轻量级文档记录,以及结构化的权衡分析。这些正是让大型团队能够有意识地、而非无意间地对架构进行推理的工具。
关键原则
- 架构关乎权衡,而非唯一正确答案。 每一项重大决策都是以一种质量属性换取另一种质量属性;工作的关键在于有意识地、透明地做出这些取舍。
- 质量属性就是需求。 性能、可用性、安全性和可维护性必须以对待功能特性同样的严谨程度加以明确规定,否则它们就会在截止日期的压力下被牺牲掉。
- 并非每一项需求都具有架构显著性。 应把稀缺的设计关注力集中在那些塑造结构、难以更改或风险较高的需求上。
- 架构必须能够演进。 大规模的前期设计之所以失败,是因为在项目起点时人们的认知最为匮乏;应当增量式地进行设计,并用自动化检查来保护关键属性。
- 记录决策,而不仅仅是绘制图表。 一项选择背后的推理过程(以及被否决的选项)比结果的图示更有价值。
- 让架构对未参与创建它的人也清晰易懂。 新加入的成员、审计人员和未来的维护者,必须能够重建出当初的设计意图。
- 能推迟的决策就推迟,必须做的决策就当机立断。 在变更成本低的地方保留选项的开放性;只有在延迟承诺代价高昂的地方才尽早做出承诺。
建议
将质量属性规定为可衡量的场景
诸如“系统应当快速”或“高可用”之类含糊的目标,既无法测试,也无法强制执行。取而代之的是,把每一项质量属性写成一个具体的场景,包含刺激、上下文和可衡量的响应:“当并发用户峰值达到 50,000 时,95% 的搜索请求应在 300 毫秒内完成。”要覆盖对你所处领域而言重要的各项属性:可用性、性能、可扩展性、安全性、可维护性、可观测性、无障碍性、可移植性和成本效益。要把它们的优先级明确排出来,因为你不可能同时把所有属性都最大化。一个为最大一致性而调优的系统,不可能同时做到最大可用性。
识别架构显著需求(ASR)
要专门留出时间,把架构显著需求(ASR)从普通需求中区分出来。如果一项需求涉及众多组件、满足成本高昂、施加了严格约束,或者存在技术风险,那么它就具有架构显著性。监管强制要求(数据驻留、数据留存、可审计性)、高负载场景、与遗留记录系统的集成,以及严格的安全边界,通常都属于 ASR。要维护一份简短的、持续更新的 ASR 清单,并把重大设计决策回溯关联到这份清单上,让评审者能够看出架构为何呈现出目前的样子。
采用演进式架构和适应度函数
要把架构当作一个按引导方向逐步演变的事物来对待,而不是一份固定不变的蓝图。适应度函数是一种自动化的、客观的测试,用来验证某项特定架构特性是否依然保持:例如,一项构建时检查,确保没有模块从被禁止的层导入代码;一项性能测试,如果 p99 延迟出现回退就使流水线失败;一项安全扫描,拦截已知存在漏洞的依赖项;一项测试,确认没有服务直接连接另一个服务的数据库。适应度函数把架构意图转化为持续强制执行的护栏()这是在一个庞大且不断变化的团队中,让这种意图始终保持有效的唯一方式。
使用 C4 和 arc42 进行文档记录
使用 C4 模型在四个缩放层级(系统上下文、容器、组件和代码)上描述结构,这样每一类受众都能阅读适合自己的层级,而不必让单张图表承担全部信息。使用 arc42 作为周边叙述内容的模板:目标、约束、上下文、解决方案策略、构建块、运行时场景、部署、横切关注点、决策和风险。把每一项具体决策记录为简短的架构决策记录(ADR):背景、决策、状态和后果,每个决策一份文件,与代码一起进行版本管理。如果你只能培养一个文档习惯,那就选 ADR:对大型团队而言,它带来的回报超过其他任何做法。
开展结构化权衡分析,并以风险驱动设计
对于高风险系统,可以采用诸如 架构权衡分析方法(ATAM) 这样的方法,依据已排定优先级的质量属性场景来权衡候选架构。它能够揭示敏感点(某项决策强烈影响某一项属性的位置)和权衡点(某项决策同时影响多项属性的位置)。若想采取更轻量的做法,可以采用风险驱动设计:设计投入的力度应与风险大小成正比。风险低、已被充分理解的部分不需要太多仪式化流程;而新颖的、影响重大的或不可逆的决策,则值得投入原型验证、探针实验和正式评审。
权衡:优缺点对比
| 方法 | 优点 | 缺点 |
|---|---|---|
| 大量前期架构设计 | 协调清晰;在固定范围的项目中较少出现后期意外 | 决策是在认知最匮乏时做出的;进展缓慢;难以应对变化 |
| 涌现式/演进式架构 | 能够随学习而调整;浪费更少;支持快速交付 | 若缺乏适应度函数,则存在漂移风险;需要强有力的工程纪律 |
| 正式的 ATAM 式评估 | 严谨、可审计,能够揭示隐藏的冲突 | 耗费时间和专业能力;对小改动来说是杀鸡用牛刀 |
| 轻量级 ADR + C4 | 成本低、易读、渐进式,可扩展到众多团队 | 效果完全取决于维护它们及时更新的纪律性 |
其中的核心张力在于确定性与适应性之间的博弈。固定价格的政府项目和安全攸关系统更倾向于加大前期的严谨程度和正式评估,因为后期变更或失败的代价极为高昂。快节奏的产品型组织则更倾向于采用由自动化把关的演进式方法。大多数大型组织两者都需要:对不可逆的、影响重大的决策以及横切关注点实施更重的治理,而在其他方面则采用更轻量、涌现式的设计。两个极端各有各的失败方式:架构过度会浪费数年时间却交付不出任何成果,架构不足则会产生一团既无法扩展、也无法审计的乱麻。
与团队讨论的问题
当你的两项质量属性在负载下发生冲突时,哪一项胜出?你是否把这个优先顺序写了下来? 每一种架构都会强制做出取舍:最大一致性会削弱可用性,严格的安全性会增加延迟,激进的缓存策略会与可审计性相冲突。在大型团队中,危险之处在于不同的小组会默默假定不同的优先级,于是一个小组为吞吐量而优化,另一个小组却坚守严格一致性,而这种冲突往往要到发生事件时才会浮出水面。在企业和政府环境中,监管机构会问你保护了哪一项属性、为什么,因此这一优先排序必须是明确且站得住脚的,而不是靠口口相传。把你的质量属性场景拿出来,两两配对,大声地对比排序,直到顺序变得毫无歧义。然后把胜出的属性编码为一个适应度函数,这样这一优先级就能在截止日期的压力下保持不变,而不会逐渐被侵蚀。
你最近的哪些决策属于单向门,它们所受到的审视是否比双向门决策更严格? 风险驱动设计主张,设计投入的力度应当与一项决策的逆转难度成正比,然而大多数团队却以大致相同的仪式化程度审查每一项变更。这会把注意力浪费在成本低、可逆的选择上,而那些不可逆的决策(比如固化进法律记录中的数据模型、公开的 API 契约、核心数据存储)却在缺乏足够挑战的情况下悄然通过。找出上个季度的重大决策,按可逆程度排序,然后问一问那些不可逆的决策是否经过了原型验证、探针实验或正式评审。在长期存续的企业和政府系统中,一次错误的单向门决策所带来的代价会在十年间不断累积,因此额外投入的严谨性能带来数倍的回报。让流程的力度与决策的可逆程度相匹配,而不是与代码差异量的大小相匹配。
对于你即将做出的下一个高风险、难以逆转的决策,谁需要在场?你将依据哪些场景来给各个选项打分? ATAM 式的结构化权衡评审,只有在一项决策不可逆且同时涉及多项质量属性时,才值得付出这份成本,而它的力量来自于在场的人员:交付、安全、运维,以及承受后果的政策或业务负责人。少了其中任何一种声音,你就会在构建完成之后才发现冲突()就像一个缓存决策可能悄悄破坏了一项可审计性需求那样。把已排定优先级的质量属性场景作为评分标准,寻找敏感点(某个选项对单一属性造成剧烈摆动的地方)和权衡点(某个选项同时影响多项属性的地方)。你想要得到的产出,是一份简短的 ADR,记录下被否决的选项及其原因,这样即使做出决策的人已经离开,推理过程依然能够留存下来。如果眼下没有任何即将做出的决策看起来值得这样做,那这一点本身就值得警惕,因为一个在可预见范围内没有任何不可逆决策的大型项目,通常是因为看得不够远。
如果一位新加入的成员或外部审计人员只能看到你写下的架构文档,他们能否重建出系统为何呈现出目前这个形态?你上一次测试这一点是什么时候? 只存在于少数资深人员脑海中的架构,是一个单点故障:一旦这些人离开,每一项难以逆转的决策背后的推理也会随之流失,下一个团队只能通过一次次事件重新学习这些知识。对大型组织而言,架构的易读性(与现实相符的 C4 图表、arc42 叙述文档,以及记录被否决选项的 ADR)正是让数十个团队能够在不开会的情况下就同一个系统进行推理的关键。拿出一份最近的 ADR 和一张最新的图表,交给一位没有参与该组件构建的人,看看他们在不得不去问人之前能够理解到什么程度。在企业和政府环境中,审计人员正会做这样的练习,而描述去年系统状态的文档比完全没有文档还要糟糕,因为它会误导那些必须为其做出认证的人。把书面记录的新鲜度当作一项可衡量的属性来看待,并用适应度函数或评审节奏来保证它的真实性。
你的哪些架构特性今天已经受到自动化适应度函数的保护,又有哪些仍然依赖于每个人记住规则? 只存在于 wiki 页面或评审者记忆中的意图,一到截止日期临近就会被侵蚀,因为分层规则、禁止共享数据库的边界,以及延迟预算,恰恰正是团队在压力之下最先舍弃的东西。在一个庞大且快速变化的代码库中,唯一能够存续下来的意图,是被构建过程强制执行的意图,因此你所声称拥有的特性与你实际检查的特性之间的差距,正是你真正的架构风险所在。列出你的重要特性,并逐一标注为已强制执行、人工评审或无人把关,同时回顾最近三次评审发现漂移的情况()本来适应度函数原本可以更早发现它们。在受监管的系统和公共系统中,这一点的重要性加倍,因为监管机构问的不是你是否打算实现数据驻留或可审计性,而是你如何证明这一点被持续保持()一条绿色的流水线远比一份政策文件更有说服力。优先把那些失败可能性高且代价昂贵的特性自动化,同时接受有些特性仍需依赖人工。
当你判断一项需求是否具有架构显著性时,是谁在做这个判断?你如何防止 ASR 清单变成“包罗万象”或“形同虚设”这两个极端? 命名架构显著需求的价值,来自于筛选的严格性:如果把每一项需求都当作显著需求,设计就会陷入停滞;如果不把任何需求当作显著需求,那些结构性的、有风险的、难以更改的需求就会在无人把关的情况下溜走。在大型团队中,容易出现的倾向是让每个小组各自局部判断,这会导致标准不一,并在跨团队场景中产生意外()某个小组眼中的“小事”,却限制了另一个小组的结构选择。拿出你当前的 ASR 清单、所使用的判断标准(涉及众多组件、满足成本高昂、约束严格、技术风险高),以及几项处于边界地带的需求,当场大声地测试这一边界。对企业和政府而言,诸如数据驻留、数据留存和可审计性这类监管强制要求,几乎总是具有显著性且不可协商的,因此要明确谁拥有这份清单的所有权、它如何被评审,以及增删 ASR 的决定如何被记录()因为一份无人治理的 ASR,就是一项在审视之下无人会为之辩护的需求。
行业视角
初创企业。 让仪式化流程接近于零,同时让记录接近于完整。可以跳过正式的 ATAM 研讨会和重量级模板,但仍应为那些一旦要逆转就会很痛苦的选择(数据存储、单体还是微服务、身份认证提供商)撰写十几份简短的 ADR,并把你早期客户真正能感受到的两三个质量属性场景固定下来。你稀缺的资源是工程关注力,因此只需保护那些一旦失效就会致命的特性,比如租户隔离,其余的一切都可以保持涌现式、且改动成本低廉的状态。
小型企业。 在没有专职架构师、预算又紧张的情况下,要依靠那些几乎不花钱的基础做法:把你为数不多的质量属性表述为具体数字,为任何难以逆转的事项撰写 ADR,并让你所选择的平台或供应商去承担繁重的结构性决策。优先选择购买一套有良好支持的技术栈,而不是自建定制基础设施,并把供应商已记录的架构当作你继承下来的约束,而不是需要从零开始撰写的东西。
企业。 面临的挑战在于跨越众多团队和数年路线图的一致性,因此应当投资于共享机制:一个架构公会、一套共同的质量属性场景、与代码一同存放的 ADR,以及在 CI 中运行的适应度函数()这些边界即便单个评审者也无法在这种规模下靠人工把关。要对不可逆的、横切性的决策使用结构化权衡分析,把 C4 图表作为设计评审中的共享地图,并集中治理 ASR 清单,防止各小组做出局部合理、却在全局层面相互冲突的选择。
政府。 长期存续、受到监管的系统,使得有文档记录、经得起推敲的架构成为采购和问责方面的硬性要求,而不是锦上添花之举。应把数据驻留、数据留存、可审计性和无障碍性当作架构显著需求,写入一份审计人员可以直接阅读的 arc42 描述文档中,并开展包括政策和安全官员在内的轻量级权衡研讨会,使诸如缓存与可审计性之间的冲突之类的问题,能在写进代码之前先在纸面上浮现出来。要让推理链条保持足够完整,使负责任的官员能够展示出应尽的注意义务,并且优先选择具有清晰退出选项的架构,而不是让公共机构在长达十年的时间里被锁定在单一供应商身上的架构。
示例
初创企业。 一支六人组成的种子期 SaaS 团队,把架构记录保存在一份共享文档中,而不是走正式流程,但仍然会写下那些一旦逆转就会很痛苦的决策。他们记录了大约十几份 ADR(为什么选择 Postgres 而不是文档数据库、为什么选择模块化单体而不是微服务、为什么选定了他们的身份认证提供商),并固定了两个对早期客户真正重要的质量属性场景:“注册在两秒内完成”和“任何客户都绝不能读取到另一个租户的数据”。当他们招募到第七位和第八位工程师时,这些记录让新人在入职第一周就能开始交付代码,而不必打断所有人去询问事情为何是这样。
企业。 一家跨国银行正在整合十二个区域性支付系统,因此成立了一个小型架构公会。该公会定义了八个质量属性场景(包括“每秒处理 10,000 笔交易且零交易丢失”和“在 15 分钟内完成某个区域的恢复”),记录了大约四十份 ADR,并在 CI(持续集成)中强制执行适应度函数:任何服务都不得写入另一个域的数据库,所有服务间调用都必须被追踪,任何带有严重 CVE(通用漏洞披露)的依赖项都会使构建失败。C4 上下文图和容器图成为每一次设计评审中的共享地图,跨团队的集成争议大幅减少。
政府。 某国家级机构正在对一个福利平台进行现代化改造,法律要求它必须保证数据驻留、七年可审计性和无障碍合规性。该机构的架构师把这些视为 ASR,并把它们写入一份供审计人员直接评审的 arc42 描述文档中。他们与交付团队、安全部门和政策官员一起开展了一场轻量级的 ATAM 研讨会,比较两种候选架构,结果发现首选设计的缓存策略与可审计性需求相冲突。在写下一行代码之前,就在纸面上发现这个权衡问题,节省了数月的返工,并为负责任的部长提供了体现应尽注意义务的书面证据。
商业论证:动机、投资回报率与总拥有成本
架构基础带来的回报主要体现为避免掉的成本,这使得它很容易被低估投入,一旦省略却又代价高昂。采用它的成本并不高:几位经验丰富的架构师的一些时间、若干次研讨会、一份文档模板,以及在适应度函数上的一些 CI 投入,通常只占项目预算中很小的个位数百分比。而不采用它的成本,会在之后到来,且代价更为高昂:当未加规定的质量属性在生产环境中失效时的返工、当未记录的耦合阻碍了强制性变更时的紧急重新平台化、因无人理解系统而拖延不决的事件处理,以及导致交付停摆或触发罚款的审计失败。
面向领导层时,应当围绕可选性和风险来构建论证。良好的架构基础会降低未来变更的成本(这直接影响一个生命周期长达十年的系统的交付速度和总拥有成本),减少严重事件发生的频率和持续时间,并产生监管机构和审计人员如今所要求的文档留痕。仅 ADR 这一项习惯,就会在新一届领导层第一次问出“我们当初为什么这么建”时收回成本()因为你能在几分钟内给出答案,而不需要展开一场取证式调查。尽可能把它量化:权衡一次避免掉的重大重构,或一次避免掉的审计失败,与这些实践所带来的一点点持续成本之间的大小。
反模式与常见陷阱
- 象牙塔式架构。 架构师只产出图表,却从不接触代码,也不与交付团队沟通;他们设计出的方案要么被忽视,要么根本无法实施。
- 把质量属性当作形容词。 “可扩展、安全、可靠”却没有任何数字、没有任何场景,因此也就无从验证或权衡。
- 大规模的前期设计。 在写下第一行代码之前就把每一个细节都定死,恰恰在认知最薄弱的时候锁定了各项决策。
- 说谎的文档。 描述的是去年系统状态的图表,比完全没有文档还要糟糕,因为它会造成误导。
- 简历驱动式设计。 选择技术是为了给个人履历添彩,而不是为了满足架构显著需求。
- 过度打磨。 为需求从未提出过的规模、灵活性或通用性进行工程设计,永久性地增加了成本和复杂度。
- 没有架构护栏。 依赖良好的意愿,而不是适应度函数,来在一个庞大团队中维持结构的完整性。
成熟度模型
- 第 1 级:启动。 架构是隐性的,只存在于个人的脑海中。没有文档记录的质量属性,没有 ADR,没有共享图表。结构是在事件发生过程中被发现的,每一次跨团队依赖都要从零开始重新谈判。
- 第 2 级:发展。 一些团队会写下那些一旦逆转就会带来痛苦的决策,并勾画出关键图表,但这种做法并不一致:一个小组坚持写 ADR,另一个小组则完全不写;质量属性被表述为形容词,而不是可衡量的场景;文档在各个项目之间逐渐过时。
- 第 3 级:标准化。 质量属性场景和架构显著需求,依据一份有文档记录的、全组织统一的标准被明确规定并排定优先级。ADR 成为常规做法,与代码一同存放;C4 和 arc42 文档按照统一模板维护;所有团队在做出重大决策时都必须进行结构化权衡评审。
- 第 4 级:管理。 架构是依据基线来衡量的,而不是靠口头断言。CI 中的适应度函数会报告诸如 p99 延迟、分层违规、未被追踪的调用以及存在漏洞的依赖项等特性;ADR 覆盖率和文档新鲜度被作为指标进行追踪;权衡评审会依据已排定优先级的场景为各个选项打分;对照既定基线出现的漂移会触发一个明确定义的响应,而不是造成意外。审计人员可以依赖被度量的证据,而不仅仅是叙述性说明。
- 第 5 级:协同。 架构在整个组织范围内持续地、适应性地演进。适应度函数和事件数据被反馈回来,用以决定哪些特性重要、设计投入应放在何处;ASR 清单、质量属性优先级和护栏,会随着强制要求和风险的变化而重新调整范围;这一实践与交付、安全和风险规划相集成,使平台能够适应新的需求,而不必从零开始重建。
讨论思路
- 对你最关键的系统而言,哪三项质量属性是真正不可协商的?你今天能否把每一项都表述为一个可衡量的场景?
- 你如何判断一项决策是否“具有架构显著性”,从而值得写一份 ADR,而不是直接去做?
- 在哪些地方,适应度函数能够捕捉到你当前代码评审所漏掉的漂移?
- 你的组织是架构设计过度,还是架构设计不足?有什么证据能说明是哪一种情况?
- 在一个“团队的团队”结构中,谁对架构负责?你如何同时避免象牙塔和彻底的无政府状态?
- 一位外部审计人员,仅凭今天已写下的内容,将如何重建出你架构背后的设计意图?
要点总结
- 架构是一系列逆转成本高昂的决策集合;要有意识地做出这些权衡,并把它们记录下来。
- 把质量属性表述为可衡量的场景,并识别出那些塑造结构的架构显著需求。
- 增量式地进行设计,并用自动化适应度函数保护关键架构特性。
- 使用 C4 图表、arc42 叙述文档,以及与代码一同存放的、按决策分文件记录的 ADR,以轻量但真实的方式记录文档。
- 让严谨程度与风险相匹配:对不可逆、影响重大的决策采用重度分析,其余场合则采用轻量流程。
- 商业价值体现在避免返工、缩短事件持续时间、加快未来的变更速度,以及提供随时可供审计的证据。
参考文献与延伸阅读
- Len Bass、Paul Clements 和 Rick Kazman 合著,Software Architecture in Practice
- Neal Ford、Rebecca Parsons 和 Patrick Kua 合著,Building Evolutionary Architectures
- Simon Brown 著,Software Architecture for Developers(以及 C4 模型)
- Mark Richards 和 Neal Ford 合著,Fundamentals of Software Architecture
- George Fairbanks 著,Just Enough Software Architecture: A Risk-Driven Approach
- Michael Nygard 著,“Documenting Architecture Decisions”(ADR 模式的出处)
- Gernot Starke 和 Peter Hruschka 编写,arc42 文档模板
- Paul Clements 等合著,Evaluating Software Architectures: Methods and Case Studies(ATAM)
- ISO/IEC 25010,Systems and software quality models