在之前的文章中,我们探讨了决策如何成为软件系统架构设计的基础。“软件架构可能不是你想象的那个样子”一文认为,软件架构描述的是决策而非结构;而“为什么需要关注软件架构”一文则指出,进一步挖掘架构中隐含的决策,并迫使这些决策变成显式的,有助于开发团队利用他们从冲刺和迭代中获得的经验数据做出更好、更明智的决策。
在团队所做的架构决策中,最重要的是选择他们将要使用的框架,将要引入的模式以及将要使用的策略。对其中每一项的好处和局限性的考量将影响团队的决策和系统最终的架构。
框架、模式、策略可以帮助团队更快地设计出应用程序的最小可行架构(MVA),但在使用它们时,团队需要考虑一些可能出现的问题,以免在创建完 MVA 后发现框架、模式和策略做了一些不恰当的隐式决策,而不得不大量进行重写。要进一步了解 MVA,可以读下“最小可行产品与最小可行架构”和“最小可行架构实践:构建家庭保险聊天机器人”这两篇文章。
软件框架
软件框架是抽象、可扩展的代码库,开发人员可以把它们应用于特定的目的。没有软件框架,现代软件系统都没法构建,因为它们让开发人员可以专注于增加价值的特性,而不用开发每个系统都需要的所有辅助性软件。框架可以大幅提升生产力,但也是有代价的,代价就是必须按框架要求的方式工作,这可能会起到副作用,如隐蔽的安全漏洞或架构缺陷。
不要让框架接管你的应用程序。框架会有接管应用程序的倾向,有时候甚至会影响开发团队看待问题的方式。例如,团队使用了规则引擎框架,它在处理基于数据的决策逻辑时非常强大,但我们也看到,为了使用框架提供的一些吸引人的特性,团队开始将应用程序的所有东西都改造成规则。如此一来就会错过一些更简单、更直接的解决问题的方式。
理解框架替你做的决策。在软件架构场景中,框架有替你做架构决策的效果。大多数框架的问题是,它们所做的架构决策是隐式的,不透明。系统也许能接受这些决策,也许不能接受;这就需要你自己做好功课,确定框架是否适合。你甚至需要使用这个框架构建系统的某个关键部分,以此来验证它是否满足系统的 QAR。
例如,你可以使用一个开源、可重用的框架(如 Spring 框架)来开发各种 Java 应用程序,它已经成为 Java 社区中非常流行的框架。由于这个框架很大,而且相当复杂,所以开发人员需要接受相当数量的培训并具备丰富的经验才能有效地使用它。
Spring Boot 是 Spring 框架的一个扩展,初级水平的开发人员就可以使用它快速创建独立的生产级应用程序。Spring Boot 开发效率高是因为它做了许多设计决策,它选择了“最佳”配置(Spring Boot 设计人员的说法),使用了 Spring 平台和第三方库。
选择一个像 Spring Boot 这样的框架可以大大加速应用程序开发和实现,但是开发人员有必要深入了解框架代替他们做出的“隐藏”决策,因为应用程序是需要持续演进的,随时可能需要调整甚或逆转这些决策。在 Spring Boot 中,这些决策包括使用哪些默认配置,需要安装哪些包来提供应用程序需要的依赖。
定期关注框架的发展。和任何代码一样,框架也会有固有的缺陷,包括可能已存在多年但未被发现的安全漏洞。和组织自己管理的代码不同,框架常常依赖外部力量完成升级,比如其他公司或开源项目的维护者。当框架有新版本发布时,组织需要将使用该框架的应用程序升级到最新版本。使用框架的旧版本是安全漏洞的主要来源,它们可能带来不为人所注意的重大风险。
不要因为大公司在使用就选择一个框架。有些人在选择框架时会落入这样的陷阱:因为有一些知名的大公司在使用这个框架,甚或是开发了这个框架,所以它必然很好。毫无疑问,它在某些方面确实很好,但那家大公司面对的场景和挑战与你可能并不相同。无论你决定是否评估该框架的质量属性,你都是在做一个架构层面的决策。这时,最好是相信你自己的数据,而不是其他人所做的工作。
不要让团队的技能衰退。因为框架是以一种透明的方式来处理复杂的问题,所以,使用框架的开发人员就会丧失甚至永远无法培养起理解或开发被框架接管的那部分代码的能力。结果就是,他们可能不理解他们在选择一个特定的框架时自然做出的架构选择。
因此,使用框架不能代替合理的架构设计;做架构决策的开发人员必须了解框架所做的权衡,以及在什么情况下,那些权衡将无法接受。关于这方面的知识,有些可以从同其他使用该框架的开发人员的交流中获得,它们了解框架所做的假设,以及这些假设会带来什么局限。也许从社区获得的知识就可以帮我们将某些框架排除在考虑范围之外,但对于任何框架的终极测试都是根据质量属性需求(QAR)来测试系统。
计划好替换框架。如果不得不替换框架,如框架出现了你不希望看到的变化,或者即将到达生命周期的终点 / 不再支持,那么理解选择该框架时所作的决策就尤其重要。框架变化不定,如果没有计划好框架过时后如何替换,那么开发团队将面临成本高昂的重写或替换。即使是商业框架也可能走到生命周期的终点,因为并购或是不断变化的商业条件。了解使用替代框架调整系统的成本始终是架构决策的一项重要内容。
编程语言已经成了隐藏的框架。将编程语言视为一种框架,这个值得花点时间来说一下。起初,高级语言的功能只比底层硬件抽象多一点点,但它们的范围逐步扩大,到现在,几乎所有的现代语言都包含了大量处理某些问题的库,有些甚至对处理某些问题的库做了专门优化。即使是像 COBOL 这种相对“古老”的语言,也让使用它们的程序员认识到,如果语言可以有效地解决某类问题,就可以节省时间和精力,如果不能,就会增加工作量,而且会导致某种无法解决的问题(例如使用 COBOL 解决矩阵代数问题)。编程语言的选择是一个团队可以做出的最具架构意义的决策之一。
生态系统代表框架的极端情况像亚马逊云科技(AWS)或微软 Azure 这样的生态系统提供了可以完美协同的完整框架族。在选择这样的生态系统时,团队隐式做了大量的决策,关于他们的工作方式,以及他们的系统解决各种问题的方式。
我们关于框架的所有观点都适用于生态系统,但到了一个很极端的程度:团队需要了解生态系统及其框架所做的决策,因为放弃该生态系统另选一个的代价将是大量的重写应用程序。在应用程序开发的早期,你需要确定自己是否已经准备好做出这样的承诺。
关于生态系统,我们需要考虑的一点是,供应商的终极目标是将生态系统的用户留在生态系统内。为此,迁移到一个不同的生态系统而又不大量的重写应用程序几乎是不可能的。一旦决定加入一个生态系统,组织还需要警惕使用生态系统的成本随着时间的推移上升,因为供应商知道,离开生态系统的成本非常高。不应该认为入门级的价格会永远持续下去。
模式
模式是可重用、已证明有效的解决方案,旨在解决特定场景下常见的软件设计问题。架构模式可以看作是架构设计决策包,在使用一个模式时需要完全理解这些决策。理解该解决方案的适用场景至关重要。在合适的场景中使用模式会很有效。它们让开发人员可以利用已经在用的合理的设计方法,更快速地构建出更好的软件。也许,更重要的是,如果模式定义和描述以为大部分 IT 从业者所接受,那么它们就可以提供一种有用的通用语言,来描述软件挑战以及克服挑战的潜在方法。
模式可能比你最初想的要难用。模式通常”只是“概念性的;也就是说,它们没有用代码实现,仅仅是算法性质的。这不是说它们缺乏价值,因为有时候,有一个看问题的新方法是制定出优秀解决方案的关键。但是,因为模式只是概念性的,所以一般来说,用代码实现它们有时候相当具有挑战性;模式是通用的,为的是可重用;实例化模式,创建适用于特定场景的设计需要相当的经验。
此外,如果未能充分隔离问题的话,当模式运用不恰当时,就可能会导致非必要的复杂架构设计和费解的应用程序代码;模式越是通用,潜在的问题就越不可能隔离,这反过来降低了模式的通用性和可重用性。此外,模式可能并未考虑它们提供的解决方案对部分 QAR 的影响,如可扩展性或性能,可能需要辅以适当的策略来解决这种不足。提出诸如“它有帮助吗?”、“它有用吗?”和“它可测试吗?”这样的问题,有助于评估一个模式的潜在用途。
模式也做决策。理解与特定模式相关的设计决策,以及在实现模式时需要做出的决策,是成功使用一种模式的关键因素。就像在之前的文章中提到的那样,架构的本质是对产品的技术方面进行定义和限制的一系列决策。不管团队采用什么方法,这些决策都存在。使用一种模式的结果就是团队做出了大量设计决策,有意的或是默许的。
例如,分层架构模式是一个在软件架构师、设计人员和开发人员中间非常流行的模式。它将软件系统分成多个单元(“层”),每个层都可以单独开发和演进,层与层之间的交互降至最低。模式本身并没有规定应该使用哪一层,以及应该实现多少层。它也没有指定应该使用什么技术实现这些层,层与层之间如何交互,或者应用程序代码应该如何打包并运行。该模式经常实现为 4 层架构(展现层、业务逻辑层、数据访问层、数据存储层),代码则打包在 3 层中(展示层、应用程序层、数据层),如图 1 所示。
分层架构模式实现示例
不过,诸如多少逻辑层(layer)、多少物理层(tier)或者应该使用什么技术这样的决策留给了实现团队,他们要根据自己需要满足的 QAR 来做出。因此,使用这个模式可能会产生不同的系统设计,这由上述决策决定。
在模式的概念和实现之间,有些东西可能会丢失。不单是模式会如此,但使用模式放大了这个问题:在分层架构模式中,层次本身似乎非常清晰,但在写好的代码中就几乎没什么东西能实际地区分或硬性划分层了,可执行代码中更是如此;层只是开发人员头脑中的一个概念。因此,通常很难测试应用程序开发是否严格遵守了这个模式。如果对团队而言严格遵守模式很重要,就要发明一种方法来评估模式遵守情况。
策略
策略是实现一个或多个质量属性需求的决策。它们比模式更具体,比实现更简单,但可能产生需要处理的副作用。它们提供了满足 QAR 的有效方法,以软件架构师和工程师多年来获得的知识和经验为基础。
务必选择恰当的架构策略。事实证明,架构策略的选择和应用是处理特定质量属性需求的有效方法。架构策略是一个知名的设计理念,这来自卡内基梅隆大学软件工程学院(SEI/CMU)的研究,最初是为了解决架构模式存在的一些不足。一个架构策略是一个影响系统实现一个或多个质量属性需求的决策。通常(遗憾的是并非总是如此),决策会分门别类地记录在文档中,为的是促进这些知识在架构师之间重用。
例如,数据分发是一种有效可扩展性策略。数据分发涉及针对具体的服务进行数据分区,根据某些标准划分数据库行,如客户标识。这个策略应该用于处理大型工作负载时可能遇到问题的特定数据库。首先关注数据库可扩展性是处理特定可扩展性需求的好方法,因为数据库通常是软件系统中最难扩展的组件。
架构策略帮你解决质量属性需求。功能性需求通常有很好的文档记录,并且经过了业务干系人的仔细审核,而 QAR 可能没有那么好的文档记录,也没有经过那么仔细的审核。它们可能只是通过一个简单的列表提供出来,只有一页。它们没有经过仔细审核,往往都是用一些陈词滥调进行描述,如“必须可扩展”、“必须高可用”。
然而,我们的观点是,QAR 推动了架构设计。我们需要确保架构决策能满足质量属性。这些决策常常是折中方法,因为一个可以更好地实现某一项 QAR 的决策可能对实现其他的 QAR 存在不良的影响。准确理解 QAR 和需要做出的权衡是妥善设计系统架构最为重要的前提条件之一。架构决策常常是以找出可以平衡竞争性 QAR 最不坏的方法为目标。每一个决策都可能有副作用,在某种程度上使决策失效。没有所谓的“正确答案”,只有“对于特定场景已足够”的决策。这些决策可以利用架构策略来实现,在使用策略实现它们之前,完全理解决策很重要。还有一点也很重要,就是要时刻记住持续架构原则 #3“延迟设计决策,直到它们绝对必要”,切忌用不必要的策略进行过度设计。
小结
在真正需要之前,不要对任何特定的框架、模式或策略过多投入。相反,要利用 MVA 的概念做出实现 MVP 所需的最少决策,了解正在做出的每一项决策,然后由自己决定那些决策是否适合实现你的 QAR。
在某种程度上,可以将框架、模式和策略进行组合。例如,可以用 Spring Boot 开发使用了分层架构模式的软件系统的某些部分。如果模式没有考虑它提供的解决方案对某些 QAR 的影响,则可以使用策略来处理模式的不足。不过,你要时刻记住,不管是框架还是模式,它们替你做的那些决策,其中有一些在你试图组合框架和模式时可能会相互冲突。
随着你演进 MVP,增量地采用框架、模式和策略,满足产品增量演进的需求即可。涉及生态系统的决策尤其要谨慎,因为那是些不从头再来就很难逆转的决策。而且,自始至终都不要忘记自己的目标,要验证关于框架、模式和策略的假设是否有效,你决定使用它们的决策是否有助于满足你的 QAR。
作者介绍:
Kurt Bittner 拥有超过 30 年短周期交付软件的经验。他帮助过许多采用敏捷软件交付实践的组织,包括大型银行、保险、制造和零售企业,以及大型政府机构。他曾为大型软件交付企业工作,包括甲骨文、惠普、IBM 和微软,并曾是 Forrester Research 公司的技术行业分析师。他的重点领域是帮助组织建立强大、自组织的高效团队,交付受客户欢迎的解决方案。他撰写了 4 本与软件开发相关的书,包括《Nexus 规模化 Scrum 框架》。他现居科罗拉多州博尔德市,并担任 Scrum.org 的企业解决方案副总裁。
Pierre Pureur 是一位经验丰富的软件架构师,拥有丰富的创新和应用程序开发背景、广泛的金融服务行业经验、广泛的咨询经验和全面的技术基础设施知识。他曾担任一家大型金融服务公司的首席企业架构师,领导大型架构团队,管理大型并发应用程序开发项目,指导创新计划,以及制定战略和业务计划。他是“Continuous Architecture in Practice: Scalable Software Architecture in the Age of Agility and DevOps”(2021 出版)和“Continuous Architecture: Sustainable Architecture in an Agile and Cloud-Centric World”(2015 出版)的合著者,并发表了许多文章,以及曾在多个软件架构会议上发表相关演讲。
原文链接:
https://www.infoq.com/articles/frameworks-require-decisions/
评论