《架构师修炼之道》摘录06-主动选择架构

https://www.notion.so/images/page-cover/nasa_space_shuttle_challenger.jpg

本系列内容来自《架构师修炼之道》。在自己的笔记中以半摘录的方式,用 blockquote 穿插自己的思考和感悟,以加深对内容的理解和消化。

全书整体分为三个部分

  • 第一部分介绍软件架构的基础知识和架构师必备的设计思维

  • 第二部分讲解架构师需要掌握的核心技能和知识

  • 第三部分讨论一系列使用的架构设计方法

前两部分适合从头到尾通读,第三部分用于参考和检索。

第六章的标题是“主动选择架构”,本章学习运用关键架构需求制定决策,选择架构的结构。

6.1 发散探索,聚合决策

做决策意味着我们需要多个备选方案。如果没得选,那就不需要决策了。要获得备选方案,就需要进行设计探索。

设计探索时反复地发散和聚合的过程。确定问题后,就需要发散思维,探索解决问题的各种方案。找到备选方案后,又需要聚合思维,取得共识,排除不合适的方案。

c69b7e0ec891a67a

备选方案越多,我们决策的信心就越大。我们没有时间探索系统设计的所有方面和所有方案。架构师只能重点关注质量属性、架构结构,以及会对这两者产生影响的设计决策。

6.1.1 探索架构关键点

以下是架构设计通常要探索的几个方面:

探索元素及其作用,确定架构的结构组成

架构中的结构是由元素构成的。在精心设计的架构中,每个元素都有明确的作用。任何没有明确作用的元素,都应该被剔除。探索设计方案,就是探索功能各异的元素的组合形式。

探索关系及其接口,确定元素的交互方式

关系描述了架构中的两个元素如何协同工作以完成任务。组件接口就是一种关系,通信机制(比如HTTP、TCP、共享内存)和通信规则(如API、响应对象、请求数据)都定义了接口。从某种程度上说,管理接口和元素通信的规则也是一种架构。

探索问题领域,理解架构所处的环境

每类问题都有自己的术语和概念,描述了它存在的环境。领域中的概念,无论是对象还是事件,都必须在架构的相应处加以说明。对问题领域的理解得越透彻,对架构元素的划分及功能分配就越理想。

探索技术和框架,提升质量属性

现代软件开发技术都有倾向性。框架、中间件、库,任何现成的技术都带有“偏好”和“态度”,理解他们的特点才能更好地发挥其作用。而这种倾向反过来也会影响你的设计决策。

探索构建和部署方法,确保架构可以交付

架构会影响软件的构建和部署方式。如果你想实现持续交付、多位开发人员并行工作、使用特定的测试策略,那么都需要在设计架构时考虑好。

探索以往的设计,获得启发,指导决策

所有设计都是在已有设计基础上的重新设计和调整创新。大多数架构探索始于温故-审视已知的软件设计知识。设计知识可以总结成经验法则和模式。这种知识既来自你以往的工作,也来自多年来架构师社区积累的经验。

6.2 接受约束

约束是无法更改的既定设计决策。约束分为两种类型:技术约束和业务约束。面对技术约束,我们别无选择。如果系统要求用 .NET 编写,那就必须放弃你喜欢的Java框架。业务约束则比较微妙。虽然也无法更改,但它们对架构的影响比不总是那么显而易见。假设有一项业务约束时系统必须在七月底展会开幕前上限,为了满足它,你可以从一下几个方面考虑:

  • 选择有利于并行开发的模式

  • 选择有利于增量交付的模式

  • 选择团队熟悉的技术以降低风险

  • 选择支持自动化并能加快开发速度的技术

  • 不做太多设计规划,接受技术债务,开发“大泥球”

第七章《分层模式》中将会介绍什么是“大泥球”

业务约束还可能在架构之外得到满足,比如挑选工作能力强的开发人员,提前展开测试,或者将部分任务分包出去。

早期的设计决策也可能成为约束,但它们实际上不是约束。就像房屋的承重墙一样,它框定了其他东西的大致位置。但愿这种决策也是可以更改的,移动承重墙虽然代价高,但终归是可行的。一定要分清楚自己选定的约束与外界的约束。

6.3 提升质量属性

开发软件有点像制作冰沙。要做好冰沙,你必须确保选对搅拌机。搅拌机之于冰沙,正如架构之于软件。

你也许会觉得挑选搅拌机很容易。其实并非如此。你想要容易清洗的,方便安装的,噪音小的,动力强劲的,还是便携的?这些需求就相当于时搅拌机的质量属性。使用表格对比几种搅拌机的主要质量属性。

|标准搅拌机|手持搅拌机|链锯搅拌机 ---|---|---|--- 易清洗性|中|优|中 适合厨房安装|优|中|特劣 安静程度|中|优|特劣 动力|中|劣|特优 便携性|劣|特优|优 安全性|中|中|劣

三种搅拌机都具有相同的基本功能(搅拌)。它们有着可互换的部件,比如玻璃罐既能用在标准搅拌机上,也能用在链锯搅拌机上。除了质量属性之外,设计师还要考虑生产成本、制造工艺、外部接口等。

买电子设备的时候也是如此。需要在各种品牌、参数、价格、自身需求等多方面综合考虑,选出一款最适合自己的产品。这个过程是煎熬的,但是结果确实令人满意的。

同理,软件架构师也要通过选择结构提升系统的质量属性。而选择结构通常指的就是探索各种架构模式。请记住,**所有设计都是在已有设计基础上的重新设计和调整创新!**找到能够提升既定质量属性的架构模式,把这些模式作为架构设计的起点,你才能做到事半功倍!

6.3.1 借助质量属性探索架构模式

假设我们要构建一个数据驱动的Web应用。你会选择哪种架构模式?下图展示了三种模式:三层模式、发布-订阅模式、面向服务架构模式。

cc4736ec1a6af162

三层模式 每一层完成不同的任务。对Web应用来说,展现层负责U渲染,业务层运行于服务器端负验证业务规则,数据库负责存储数据。

发布-订阅模式 所有元素都将消息发布到事件总线上。组件可以订阅感兴趣的消息。事件可能不按顺序传送,也不确保传送成功(视消息系统的规则而定)。

面向服务架构模式 服务都在中央注册表注册,以便调用者可以找到它们。组件查找并直接调用这些服务,服务响应并回复信息 (出现问题则不响应)。

三种模式分别提升和抑制了不同的质量属性。你会如何选择?

多数情況下,三层模式最理想。它易于测试、部署、描述。不过这种简单性是有代价的。三层模式很难提升可伸缩性和可用性等质量属性。如果不在架构中补充其他模式,三层模式可能无法满足我们的所有需求。

发布-订阅模式具有较高的可更改性和灵活性。虽然这种灵活性很适合构建松耦合系统,但它也有缺点。在我们的数据驱动应用中,消息顺序对事件有影响,仅靠发布-订阅模式不能保证正确的消息顺序。如果选用合适的消息总线技术,也能让发布-订阅模式勉强工作,但不够干净利索。

面向服务的架构具有可更改、灵活、可测试的特点,同时它也易于扩展,比其他模式更容易提升可用性。但它是三种模式中最复杂的,架构设计的工作量最大。我们的应用采用这种模式有点小题大做了。

这三种模式都可以用来实现我们的功能需求,选择架构模式关键要看质量属性。针对既定质量属性,要分出各种模式的优劣,我们还需要做进一步的分析。

6.3.2 运用决策矩阵

决策矩阵是一个简单的表格,用于总结分析多个架构设计方案的利弊。它适合用来选择选择与架构相关的各种设计选项,包括架构模式、功能分配、技术方案等。

38a3928a7a8839db

决策矩阵的第一列列出备选特性。矩阵每一行代表一个特性,每一列时你对相应设计选项的分析判断。建议使用易于阅读的记号来记录分析结果。总之要把分析结果用清晰的方式呈现出来。

决策矩阵成形之后,最佳决策通常是显而易见的。用这种方式总结分析设计方案,可以立即排除那些阻碍实现既定系统特性的选项。

回过头来再看之前搅拌机的那个表格,你会发现它就是搅拌机的决策矩阵。

创建矩阵的过程比矩阵本身更重要。决策矩阵可以用来总结探索结果,还能用来于利益相关方就设计决策的取舍进行有效的讨论。有些人喜欢用数字来评分。作者建议不要这么做。用数字评分是一个糟糕的主意。有些人根据利益相关方的喜好调整数字,对每一列求和,考虑总体平均值,等等。那只会让事情变得复杂而低效。

6.4 为架构元素分配功能

架构中的每个元素都有功能。我们选择结构时会为每个元素分配特定的功能,以便实现所有既定的功能需求。以 Lionheart 项目为例(书中的一个例子),下面是从预算办公室收集到的功能需求。

招投标系统的用户需要:搜索该市现在和过去的合同;将结果进行分页显示:查看每个公司的基本信息,包括公司名、电话、地址、过期合同、有效合同等:查看每个合同的基本信息,包括类型、状态、到期时间、投标公司、中标公司等;订阅接收合同更新的提醒。

这些功能需求还隐含了几项功能:用户可以搜素,意味着必须建立索引:要显示合同和公司信息,就必须存储它们:能够订阅,就要求系统存储电子邮件地址;更新时要提醒用户,就要做到可以识别更新。下图展示了用于实现这些功能需求的元素

770e61172b970a7b

元素功能表记录了架构中每个元素有权执行的基本任务。通过审视影响较大的功能需求,创建功能元素表,确保每个功能都有且仅有一个元素负责。此外,架构中的每个元素都至少应该负责一个功能,否则该元素就失去了存在的意义。

为元素分配功能时,影响较大的功能需求可以作为检查清单。后续的章节中将介绍一种明确功能的方法对系统进行建模。

6.5 设计,应变而生

此前,我们学习了在理解关键架构需求的基础上探索设计方案,进行设计决策。做好关键的设计决策对于打造稳健的架构至关重要,不过在软件里唯一𣎴变的就是变化。

所有优秀的架构师都明白变更是不可避免的。通过选择决策时间点,以及将设计决策移出架构,我们可以做到“应变设计“(design for change)。

6.5.1 推迟决策

做出一个不易逆转的决定(比如架构决策)是一件大事。避免走入死胡同或走上歧路的办法是推迟决策。推迟设计决策(不到最后一刻不做决定),可以为研究和探索争取更多时间。

《敏捷软件开发工具》一书提出了 “最后责任时刻” (last responsible moment)的概念——在避免失去重要备选方案的前提下,做出决策的最晚时间。而作者建议在“最佳责任时刻”(most responsible moment)做决策。

理想情况下,最佳责任时刻就是最后责任时刻。但在实践中,最佳责任时刻要求我们留出额外的时间,以便处理无法筆控的外部依赖、建立共识、培训、验证设计。最佳责任时刻往往来得比我们想象的要来得早。以下问题可以帮助确定做出设计决策的最佳时机:

  • 不做决策是否阻碍了项目进展?

  • 决策能否解决不能再耽搁的问题?

  • 决策是否会创造更多选项或机会?

  • 推迟决策是否明显会带来更多风险?

  • 我是否理解并接受决策带来的一切后果?

  • 我有明确的理由说明为什么现在必须做这个决定吗?

  • 如果决策失误,是否有时问弥补?我能承受这样的错误吗?

即使我们能够找到最佳责任时刻,也不代表一定有足够的信息进行决策。如果出现这种情况,我们还有一个窍门可以避免情况恶化:可以把那些容易变化的东西移出架构。

6.5.2 将设计决策移出架构

如果设计决策以后容易发生变化,那么它就不再是一个架构问题。在可能的情况下,应该把容易变化的决策留给后续设计人员来做。

己知的许多编程原则也可以作为很好的设计原则。例如,将 SOLD 原则用于架构设计可以带来许多类似面向对象设计的优点。SOLID 代表 5个面向对象设计的原则:single responsibility (单一功能)、open/closed(开闭原则)、Liskov substitution(里氏替换)、interface segregation(接口隔离)、dependency inversion (依赖反转)。比如,如果架构中的元素具有单一功能,它就更容易隔离更改。再比如,创建接口清晰的元素能带来更多的灵活性。

有许多方法可以将设计决策移出架构,比如可插拔/可替换架构 (pluggable architecture )、外部配置、自描述数据、动态发现。它们都是在设计时(design time)或运行时 (runtime)更改系统的行为(而不修改架构),理想情況下也不会 对基本质量属性产生影响。

结束语

在主动选择架构方案时,首先利用发散思维,在接受约束(技术约束和业务约束)的前提下,从架构的关键点出发,探索可能有用的备选方案。

按照系统预期的质量属性要求,将各方案进行对比,并运用决策举证,找出适合的架构模式。

在最终做决策之前,遵循”应变设计”的思路,推迟决策或者将容易变化的东西移出架构。保证基本质量属性不受影响。