前言
- 软件框架极大地提高了团队的生产力,但在这样做时,它们会做出并非总是容易看到的决策。选择框架时,要考虑它如何处理影响应用程序架构的决策,包括如何处理质量属性要求(QARs)。
- 不要让框架完全接管你的应用程序。虽然框架为你做出了关键决策,使某些事情变得更容易,但也有代价:它会限制你在其他方面的灵活性,包括如何使用其他框架或者是否可以使用其他框架。
- 从框架中脱身并不容易。它们往往会影响你的应用程序如何看待问题空间。这是一个经典的“拿着锤子就把一切都当成钉子”的问题;例如:如果你选择使用规则引擎,你开始考虑将所有条件逻辑处理为规则的应用,即使这并不是处理问题的最佳方式。
- 设计模式也会做出决策。了解与特定模式相关的设计决策,以及在实现该模式时需要做出哪些决策,是成功使用模式的关键因素。
- 架构策略可以帮助你解决QARs。你需要做出架构决策来满足QARs,这些决策可以通过使用架构策略来实现。在决定使用策略来实现这些决策之前,充分了解决策及其影响是很重要的,不要通过使用不必要的策略来过度设计。
在之前的文章中,我们探讨了决策是构建软件系统的基础。在软件架构:可能和你想的不太一样一文中,我们认为软件架构是关于捕获决策,而不是描述结构;在为什么你应该关注软件架构一文中,我们指出,对开发团队正在做出的隐含架构决策有更深入的了解,并迫使这些决策被明确地做出,可以帮助他们利用从他们的迭代中获得的经验数据做出更好、更明智的决策。
团队将做出的最重要的架构决策之一是他们将使用的框架、采用的模式和使用的架构策略。对每种选择的优劣考虑将塑造团队做出的决策以及系统的最终架构。
框架、模式和策略可以帮助团队更快地开发其应用程序的最小可行架构(MVA),但团队需要考虑一些潜在的问题,以免在后期发现框架、模式和策略隐含的决策是错误的时候,导致 MVA 需要进行大量重写。请参阅最小可行产品需要最小可行架构和【实践中的最小可行架构:创建家庭保险聊天机器人】以获取有关 MVA 的更多详细信息。
软件框架
软件框架是抽象的、可扩展的代码库,可以由开发人员根据特定目的进行适应。现代软件系统离不开它们,它们让开发人员可以专注于开发独特的增值功能,而无需开发每个系统都需要的所有支持软件。框架可以大大提高生产力,但代价是:你必须按照它们希望你的方式工作,它们可能会引入不良副作用,如隐藏的安全漏洞或隐藏的架构弱点。
不要让框架控制你的应用程序。框架往往会接管应用程序,甚至到了让开发团队看待问题的方式都受到其影响的程度。例如,使用规则引擎框架,如果你需要处理基于数据的决策,这是非常强大的,但我们看到一些团队开始扭曲他们的应用程序,使一切都变成规则,以便他们可以使用框架的某些吸引人的特性。这样做,他们错过了用更简单、更直接的方式解决某些问题的机会。
理解框架为您做出的决策。在软件架构的背景下,框架会代表您做出架构上的决策。大多数框架的问题在于它们并没有明确和透明地展示它们所做出的架构决策。这些决策可能适用于您的系统,也可能不适用;这取决于您是否做了足够的功课,以便您可以决定该框架是否适合您。这可能会延伸到实际使用框架构建系统的某些关键部分,以便您可以验证它是否满足您系统的QARs。
例如,像Spring框架这样的开源、可重用框架可用于开发各种Java应用程序,并且在Java社区中非常流行。由于它庞大且相当复杂,开发人员需要进行大量的培训和经验才能有效地使用这个框架。
Spring Boot是Spring Framework的扩展,使开发人员能够快速创建独立的、生产质量的应用程序,并利用Spring框架,而无需太多的培训和经验。Spring Boot通过做出一些设计决策来实现这种开发人员的高效率,包括选择“最佳”的配置(根据Spring Boot设计者的观点)以及使用Spring平台和第三方库。
选择像Spring Boot这样的框架可以显著加快应用程序的开发和实现过程,但开发人员需要理解框架隐含为他们所做的决定,因为他们可能需要调整甚至逆转这些决定,以确保应用程序在超出初始发布阶段后仍然可持续。在Spring Boot的情况下,这些决定包括使用哪些配置默认值,以及应用程序所需的依赖项需要安装哪些软件包。
保持对框架的更新计划。像其他代码一样,框架也存在固有的缺陷,包括可能长时间隐藏的安全漏洞。与它们管理的代码不同,组织通常依赖于其他公司或开源项目的维护者来更新框架。即使发布了新的框架版本,使用该框架的组织也必须积极努力将其应用程序升级到最新版本。使用旧版本的框架是安全漏洞的主要来源,对不留心的人造成了重大风险,这些风险可能长时间隐藏不被注意。
不要因为某个知名公司使用了某个框架就选择它。有些人在选择框架时会陷入陷阱:因为某个知名公司使用了该框架,甚至可能是开发了该框架,所以它一定是好的。毫无疑问,在许多方面它确实是好的,但是那家大公司可能没有您所面临的相同背景和挑战。无论您决定是否评估框架的质量属性,您都正在做出一个具有架构重要性的决定。最好使用自己的数据来做出决策,而不是相信别人已经为您做好了这项工作。
不要让团队的技能退化。由于框架以如此透明的方式处理复杂的问题,使用框架的开发人员可能会失去或从未培养出理解或开发框架所替代的代码的能力。因此,他们可能不了解在选择特定框架时固有地做出的架构选择。
因此,使用框架不能替代健全的架构设计;做出架构决策的开发人员必须理解框架所做的权衡,以及这些权衡何时可能变得不可接受。一些知识可以通过与使用框架的其他开发人员交流来获取,以了解框架的假设和这些假设可能带来的限制。社区知识可能足以排除某些框架的考虑,但任何框架的最终测试都是针对其质量属性需求(QARs)测试系统。
为替换框架做好准备。如果必须替换框架,比如框架以不可取的方式发生变化或面临终止生命周期/不再支持的情况,那么理解选择框架时所做的决策就尤为重要。框架来了又去,未能计划过时可能会让开发团队面临昂贵的重写或更换。即使是商业框架也可能因为合并或业务条件的变化而被终止生命周期。了解将系统调整为框架替代品的成本始终是制定架构决策的重要因素。
编程语言已成为隐藏在背后的框架。值得一提的是,编程语言可以视为一种框架。最初,高级语言只是对底层硬件的抽象,但它们逐渐扩展到了现在几乎所有现代语言都包含大量库来处理某些类型的问题,甚至有些语言还针对某些类型的问题进行了优化。即使是相对“古老”的语言,比如COBOL,它也给程序员带来了一定的视角,当它起作用时,可以节省时间和精力,但当它不起作用时,会增加工作量,并使某些类型的问题无法解决(例如,尝试使用COBOL解决矩阵代数问题)。选择编程语言是团队可以做出的最具架构意义的决策之一。
生态系统是框架的一个极端例子
生态系统如亚马逊云服务(AWS)或微软的Azure提供了整个系列的框架,它们以协调一致的方式共同工作。在选择它们时,团队会隐含地做出大量关于它们将如何工作以及它们的系统将如何解决各种问题的决策。
我们对框架所做的所有评论都适用于生态系统,但程度更为严重:团队需要了解生态系统及其框架所做的决策,因为放弃当前生态系统选择另一个生态系统可能需要对应用进行重大改写。在应用程序开发的早期阶段,您需要决定是否愿意做出这种承诺。
关于生态系统需要考虑的一件事是,对于供应商来说,它们的最终目标是让生态系统的用户留在生态系统内部。因此,几乎不可能在不进行大规模应用程序重写的情况下转移到另一个生态系统。一旦承诺加入了一个生态系统,组织还需要警惕使用该生态系统的成本随时间上升,因为供应商知道离开生态系统的成本非常高。入门级定价不应被认为会永远持续下去。
模式
模式是可重用的、经过验证的解决方案,旨在解决特定上下文中的常见软件设计问题。架构模式可以被视为一组架构设计决策的打包,使用模式时需要充分理解这些决策。理解解决方案适用的上下文至关重要。在正确的上下文中应用模式可以是有用的。它们可以通过利用已经在使用的良好设计方法,使开发人员更快地构建更好的软件。甚至更重要的是,它们可以提供一个有用的共同语言,用于描述软件挑战和潜在的解决方案,前提是模式的定义和描述被大多数IT从业者接受。
模式可能比您最初想象的更难使用。模式通常只是概念性的;也就是说,它们并不是在代码中实现的,而只是一种算法性质的东西。这并不意味着它们缺乏价值,因为有时以新的方式看待问题是开发出优秀解决方案的关键。但是,当模式仅仅是概念性的时候,将它们实现到代码中有时对于一般情况来说是相当具有挑战性的;模式是为了可重用而泛化的,而在特定上下文中实例化模式以创建设计需要丰富的经验。
此外,当问题没有得到足够隔离时,模式的不恰当使用可能导致不必要的复杂架构设计和混乱的应用程序代码;模式越通用,底层问题的隔离可能性就越小,这反过来使得模式越不通用和可重用。此外,模式可能没有考虑到其解决方案对一些QAR(质量属性要求)的影响,例如可伸缩性或性能,并且可能需要通过适当的策略来解决这一缺点。询问“它是否有帮助?”,“它是否有用?”和“它是否可测试?”这样的问题有助于评估模式的潜在用途。
模式也会做出决策。理解与特定模式相关的设计决策,以及在实施模式时需要做出的决策,是成功使用模式的关键因素。正如我们在之前的一篇文章中提到的,架构的本质在于定义和约束产品技术方面的一系列决策。无论团队采取何种方法,这些决策都存在。使用模式会导致团队做出一系列设计决策,无论是有意识地还是默认地。
例如,分层架构模式是软件架构师、设计师和开发人员中广受欢迎的一种模式,它将软件系统划分为可以独立开发和演化的单元(“层”),各层之间的交互最小。模式本身并不指定应使用哪些层,或者应该实现多少层。它不指示应使用哪些技术来实现这些层,层之间应该如何接口,以及应用程序代码应该如何打包和运行。这种模式通常被实现为4层架构(表示层、业务逻辑层、数据访问层和数据存储层),代码打包成3层(表示层、应用层、数据层) - 见下图:
实施团队需要根据要满足的QARs, 来决策分多少层或者应该使用哪些技术等。取决于不同的决策,使用这种模式可能会导致不同的系统设计。
在模式的概念和其实现之间,有些东西可能会丢失。这不是模式独有的问题,但是使用模式会加剧这个问题:在分层架构模式中,层本身似乎很清晰,但在书面代码中往往很少,实际执行的代码中根本没有任何区分或强制模式的内容;层只是开发者心中的一个概念。因此,往往很少有可以测试以确保模式被执行的内容。如果团队认为执行模式很重要,它将不得不想出评估遵守模式的方法。
策略
策略是实现一个或多个质量属性要求的决策。它们比模式更具体,更容易实现,但可能会产生需要解决的副作用。它们提供了一种有效的方法来满足QARs,基于软件架构师和工程师多年来积累的知识和经验。
选择适当的架构策略。选择和应用架构策略是解决特定质量属性要求的成熟方法。架构策略是一个已经确立的概念,最初源自卡内基梅隆大学软件工程研究所(SEI/CMU)的研究,最初是为了解决一些架构模式的缺点。架构策略是一个影响系统如何实现一个或多个质量属性要求的设计决策。策略通常(但不幸的是并非总是)在目录中记录,以促进架构师之间这些知识的重复使用。
例如,数据分布是一种有效的可伸缩性策略。数据分布涉及将数据为特定服务进行分区,例如,通过使用某些标准拆分数据库行,例如客户标识符。这个策略应该用于可能在处理大量工作负载时出现问题的特定数据库。首先关注数据库可伸缩性,因为数据库通常是软件系统中最难扩展的组件,这是解决特定可伸缩性要求的一个很好的方法。
架构策略帮助您解决质量属性要求。功能需求通常得到了很好的记录,并经过了业务利益相关者的仔细审查,而质量属性要求可能没有得到如此仔细的记录和审查。它们可能被提供为适合一页纸的简单列表。它们可能没有受到仔细的审查,并且倾向于被陈述为真理,例如“必须具有可伸缩性”和“必须具有高度可用性”。
然而,我们的观点是,质量属性要求驱动着架构设计。我们需要做出架构决策来满足质量属性要求。这些决策通常是妥协的,因为为了更好地实现一个质量属性要求而做出的决策可能会对其他质量属性要求的实现产生负面影响。准确理解质量属性要求和权衡是充分设计系统的最重要先决条件之一。架构决策通常旨在找到在竞争的质量属性要求之间平衡权衡的最佳选项。每个选择都会产生副作用,这些副作用可能在某个时候使决策失效。没有“正确答案”,只有“适合特定情况的决策”。这些决策可以通过使用架构策略来实现,重要的是在决定使用策略来实现之前充分了解这些决策。还要牢记持续架构原则#3“推迟设计决策直到绝对必要”,不要通过使用不必要的策略进行过度设计。
总结
不要在实际需要之前对任何特定的框架、模式或策略做出过大的承诺。相反,利用MVA概念只做出实现MVP所需的最小一组决策,了解每个决策所带来的影响,并自行决定这些决策是否符合您实现质量属性要求的需求。
框架、模式和策略在一定程度上可以结合使用。例如,Spring Boot可用于开发使用分层架构模式的软件系统的部分。如果某个模式没有考虑到其解决方案对某些质量属性要求的影响,可以使用策略来解决模式的不足。然而,您需要牢记,框架和模式都在您的代表下做出决策,当您尝试将框架和模式结合使用时,其中一些决策可能会产生冲突。
在发展MVP的过程中,只需根据需要逐步采用框架、模式和策略,以演进产品增量。特别要注意生态系统相关的决策,因为这些决策几乎不可能在不重新开始的情况下被撤销。在整个过程中,不要忘记您的目标——验证您对框架、模式和策略的假设是否有效,以及您使用它们的决定是否有助于满足您的质量属性要求。