架构决策记录(ADRs)是开发团队对系统所做的架构决策的重要沟通工具。如果缺乏对什么是架构的明确定义,同时也没有其他地方来记录重要决策,ADR 可能会远离其初衷,失去焦点和效果。
ADRs 旨在揭示架构决策,以此来提高透明度和责任性。但当它变得臃肿,并包含团队所做的每一个决策时,它就成为了对立面,因为架构决策淹没在其他被扔进 ADR 的内容中,难以被轻易发现。
为什么需要 ADR?
在之前的文章中,我们观察到在动态软件开发方法中,解决方案会随着时间的推移而演变(例如敏捷开发),软件架构是由一系列关于系统如何处理质量属性需求的决策来定义的。这与以软件架构文档为主要定义的前期架构方法形成了对比。
ADR 使得架构决策变得透明,帮助开发团队澄清正在做什么以及为什么这样做,并为将来支持和增强系统的人保留这种推理过程。
当软件架构通过一系列决策的变化而演进时,这些决策是基于假设和测试这些假设的实验而产生的,开发团队需要一种跟踪他们所做的架构决策的方法。
随着时间的推移,其中一些决策可能会发生变化,开发团队需要一种方便查看这些决策的方式。即使它们没有变化,未来的团队也需要了解所考虑的选择和权衡,以便能够做出更好的系统演进决策。
ADR 的目的是什么?
对于这个问题,没有一个简单的答案。撰写这个主题的作者们认为 ADR 应该记录重要的决策,并且有些人甚至进一步说应该是架构上的重要决策。这听起来是合理的,但是很难就重要性达成一致,而且更难决定什么是架构上的。
许多团队由于没有地方记录任何重要决策,将他们认为重要的任何决策都放入 ADR 中,这就淡化了架构方面,使 ADR 变成了一个"任意决策记录"。这样做会使 ADR 负载过重,有很多不应该存在的决策,而这些决策的存在只会让真正重要的架构决策更难以看到。所以,要决定将什么放入 ADR 中,我们必须决定什么是架构的?这并不像看起来那么容易。
"架构的"是什么意思?
最近,我们陷入了一个《公主新娘》的剧情里,伊尼戈·蒙托亚(由曼迪·派廷金饰演)对维兹尼(由华莱士·肖恩饰演)说:“你总是用那个词……我觉得你并不明白它的意思。”
我们在软件领域经常提到“架构”这个词,好像我们完全知道它的意义。但是当我们更仔细地审视这个概念时,我们很难准确地界定它的含义。在本文中,我们来尝试更明确地定义我们认为的架构和非架构,并为做出决策提供更明确的标准。
“架构”这个术语在软件开发中的应用实际上是问题的一部分;它是错误的隐喻。在物理世界中,架构主要关注可用性和美学。我们所谓的“软件架构”更类似于结构工程,主要关注物理系统如何弹性地承受负载。
同样,软件架构的艺术是预测软件系统的负载并为其设计。一个关键区别是结构工程是基于几千年经验的广泛知识体系,并通过科学推导出的物理定律和数学模型来加强。软件与此完全不同。它是编码的思想,并且除了某些类型的算法之外,解决问题的标准方法很少。
一个理解软件架构的良好起点是 Grady Booch 的观察,它对比了架构和设计:
"所有的架构都是设计,但并非所有设计都是架构。架构代表了塑造系统形式和功能的一系列重要设计决策,而重要性是通过变更成本来衡量的。" (Grady Booch on Twitter)。
这个观察的重要部分是:
架构决策具有高昂的撤销成本;
架构决策定义了解决方案的基本特征或“形状”,我们将其解释为解决由系统的质量属性需求集合所定义的问题的基本方法 - 详见《软件架构实践》第 2 章进行更深入的讨论。
如果一个决策不涉及这两个方面的任何一个,我们认为它不应该包含在 ADR 中。让我们更详细地审视这个断言。
什么样的变更是昂贵的?
有些决策是昂贵的变更,但并不一定复杂,复杂指的是“在智力上具有挑战性”或者“如果做出错误决策可能会造成严重后果”。换句话说,重写代码并不复杂,真正复杂的是重新思考代码背后的概念。
举个例子,有些决策是昂贵的变更,但并不复杂,比如:
重新设计应用程序的用户界面。即使使用了 UI 框架,改变视觉隐喻可能是耗时且昂贵的,但只要这些改变不影响系统处理的基本概念,就不会产生复杂性。
用具有相同功能的另一个主要组件或子系统替换原有的组件或子系统。一个例子是从一个供应商的 SQL 数据库切换到另一个供应商的 SQL 数据库。这些变化可能需要一些工作,但转换工具会有所帮助,此外,避免使用专有功能也是有帮助的。只要新的组件/子系统支持与旧的组件/子系统相同的基本概念,这个变更不会改变系统的架构。
即使更换编程语言在架构上可能并不重要,只要这些语言支持相同的抽象和编程语言概念。换句话说,语法变化并不重要,但是对基本概念或隐喻的改变是重要的。
使用适当的转换工具,这些决策实际上可能并不会很昂贵。以前重写用户界面是昂贵的,但现代 UI 设计工具和框架使这种工作相对廉价。决定使用哪个 UI 框架、SQL 数据库或编程语言是一个实现细节,而不是架构决策。这些都是重要的决策,但它们并未达到架构决策的层面。
甚至"架构上重要"的成本标准归结为"解决方案的形状"。
"解决方案的形状" 是什么意思?
对我们来说,“解决方案的形状”指的是系统用于解决问题的基本数据结构和算法。以上面关于 SQL 数据库的观察为例,选择特定的 SQL 数据库可能在架构上并不重要,但是从使用行和列来表示基本概念转变为使用树结构或非结构化数据是重要的。搜索、排序和更新这些不同类型表示的算法非常不同,具有不同的优势和劣势,因此你的选择将显著影响系统满足 QARs 的能力。
更一般地说,对我们来说,架构决策具有以下特点:
它们涉及系统使用的基本概念以及数据结构中表示的关键抽象(例如类、类型等),这些数据结构用于在整个系统甚至系统之间共享信息。
它们还涉及使用这些数据结构的方式,即访问和操作数据结构的基本算法。
对用于表示系统基本概念的数据结构的任何更改都会影响使用这些数据结构的算法,而对算法的任何更改都会改变它们所使用的数据结构。
架构首先为系统能够解决的问题类型设定了限制,有时甚至会对开发人员看待不同解决方案的能力造成一种类似锤子-钉子的盲目性,使他们无法看到其他可能的选择。改变架构决策意味着改变系统所处理的基本概念以及处理这些概念的方式。
除了表示关键概念的算法和数据结构之外,其他选择也在塑造架构中起着关键作用,例如:
对消息传递范例的更改 - 例如,从同步到异步
对响应时间承诺的更改 - 例如,从非实时到实时
对并发/一致性策略的更改 - 例如,乐观与悲观资源锁定
对事务控制算法的更改 - 例如,失败/重试策略
影响延迟的数据分布的更改
对缓存一致性策略的更改,特别是对联合数据的更改
对安全模型的更改,特别是在扩展到单个对象或元素时的安全访问粒度。
最终,所有这些选择都会转化为不容易更改的代码,因为这些选择的代码影响分散在整个软件中,而不是局部的。如果某个东西可以局部化和封装,通常不是属于架构范畴,因为可以在不对代码产生连锁影响的情况下进行更改。
架构和决策的持久性
有时候,决策的预期寿命会使团队认为该决策是架构性的。大多数决策都会变成长期决策,因为大多数系统的资金模型只考虑了开发的初始成本,而不考虑系统的长期演进。在这种情况下,每个决策都变成了长期决策。然而,这并不意味着这些决策就是架构性的;它们需要具有高成本和复杂性,才能在撤销/重做方面具有架构上的重要性。
举个例子,选择数据库管理系统的决策通常被认为是架构性的,因为许多系统将在其整个生命周期中使用它,但如果这个决策可以轻松地被撤销而无需修改整个系统的代码,那么它通常并不具备架构上的重要性。现代关系型数据库管理系统技术非常稳定,不同厂商的产品相对易于互换,因此只要与数据库的接口进行了隔离,就可以比较容易地将商业产品替换为开源产品,反之亦然。架构上的决策是局部化数据库依赖和抽象特定厂商接口,而不是选择数据库本身。
决策的持久性在 ADR 中起作用的地方是解决可持续性和弹性的问题。可持续性涉及系统能够应对未知概率和影响的未来事件集。弹性是指系统在发生这些事件时能够抵抗失败的能力。当人们说某个系统具有可持续性时,他们的意思是相信该系统能够处理他们能够想到的一切,甚至是无法预料的事情。
何时需要做架构决策?
在过去,一个团队会在系统开发的早期创建一个软件架构文档,该文档将指导系统在整个生命周期中的开发。
当一个团队使用敏捷方法来开发系统时,决策记录(ADRs)将集体取代软件架构文档,因为它们逐步记录架构以支持系统的逐步开发。我们在之前的文章中已经描述了最小可行架构(MVA)如何与最小可行产品(MVP)增量并行演化。实际上,这意味着团队将随着解决方案的演进而逐渐做出架构决策。与软件架构文档不同,ADRs 记录的决策是一次性地预先制定的。
在所有重要决策中使用 ADRs 有什么坏处?
简而言之,它会使事情变得混乱,使真正的架构问题更难以看清。这样做会使关于基本决策的讨论变得更加困难,因为问题不明确,尤其是如果决策的影响没有完全说明。
出于多种原因,团队仍然需要记录非架构性的决策,其中许多归结为需要记录决策及其原因,以便以后有人需要解释或证明。有时,为了将决策记录在 ADR 中,决策被分类为“架构性决策”,因为没有更好的地方可以记录它。
ADR 不应该用于以下用途:
促进重用。有些人,尤其是管理者,将 ADR 视为强制实施重用的手段。他们希望看到常见的组件和子系统被重复使用,因为他们认为这样可以降低成本和简化开发。然而,只有当重复使用的组件和子系统适合并且能够带来更好的解决方案时,这种观点才是正确的。当它们不适用时,会使设计变得复杂,并且会使架构变得更糟。我们大家可能都有过这样的经历,为了使“公司标准”落实在当前问题上但并不是最佳解决方案而苦苦挣扎。结果通常是成本增加和弹性降低。促进重用是开发组织中知识共享的一个方面,但是有比在 ADR 中添加大量关于设计和代码可能重用的信息更好的方式,来促进这种知识共享。我们认为更好的方式是使 ADR 成为团队试图解决问题以及选择其方法的原因的清晰记录。
推卸责任(自我保护)。有些团队认为,通过将决策放在 ADR 中,他们可以免除该决策带来的后果。越多的人看到并明确或默认地批准 ADR,对于一个糟糕决策的责任就被稀释了。他们认为,人越多就越安全。惩罚糟糕决策的人是有毒的管理文化的表现。开发团队根据当时可获得的信息做出最佳决策。当他们获得更多信息时,通常是通过构建和部署系统,其中一些决策将会改变。减少决策变更成本的关键是通过小步增量构建系统并频繁测试假设。批评过去的决策会让人感到沮丧且无效。如果团队不必担心因决策被指责而受到责备,他们就可以专注于通过实验构建更好的解决方案,而不是使用 ADR 来使自己免受责备。
记录非架构产品决策。这经常发生是因为团队经常做出重要决策,但如果没有记录它们的地方,他们就会将它们放在 ADR 中。以这种方式滥用 ADR 会使架构更难以理解:如果每个决策都是架构决策,那么没有一个决策是架构决策。换句话说,一个变成“任意决策记录”的 ADR 已经失去了它的目的。对此有一个简单的解决办法:只需保留与架构无关的重要决策的日志。架构决策通常只能被开发人员理解,而大多数其他重要决策的记录拥有更广泛的受众。将它们分开通常会让每个人都更加满意。
结论
尽管所有的架构决策都很重要,但并不是每个重要的决策都是架构决策。创建架构决策记录和其他重要决策的分离有助于提高组织间的沟通。ADR 中包含了通常不会引起广泛兴趣的技术讨论,将它们分开使系统的架构更易于理解。
如果 ADR 只关注架构,它们可以让人了解团队在权衡选择和取舍时的思考过程的演变。决策从来都不是错的,它们只是团队在某个时间点上思考的表现。是的,随着时间的推移,他们可能会选择不同的方法,但保留这种演变的记录是有用的。了解团队的思维如何演变可以提供对当前和未来权衡的见解。
在软件架构中,往往没有完美的解决方案,只有需要平衡的“不太完美”的替代方案。能够更清楚地看到这些选择有助于当前和未来的团队更好地理解他们可能需要做出的权衡。
原文链接:
https://www.infoq.com/articles/architectural-decision-record-purpose/
评论