《架构整洁之道》读书笔记(下)

第十五章:什么是软件架构

什么是“软件架构”呢件架构师的工作内容究竟是什么项工作又是什么时候进行的呢/p>

  • 身份:软件架构师自身需要是程序员,并且必须一直坚持做一线程序员。如果不亲身承受因系统设计而带来的麻烦,就体会不到设计不佳所带来的痛苦,接着就会逐渐迷失正确的设计方向。
  • 工作:软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
  • 目的:为了在工作中更好地对这些组件进行研发、部署、运行以及维护。
  • 目标:支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。

如果想设计一个便于推进各项工作的系统,其策略就是要在设计中尽可能长时间地保留尽可能多的可选项。

1. 开发(Development)

一个开发起来很困难的软件系统一般不太可能会有一个长久、健康的生命周期,所以系统架构的作用就是要方便其开发团队对它的开发。

不同的团队结构应该采用不同的架构设计。

  • 对于一个只有五个开发人员的小团队来说,他们完全可以非常高效地共同开发一个没有明确定义,组件和接口的单体系统(monolithic system)。

这样的团队可能会发现软件架构在早期开发中反而是一种障碍。这可能就是为什么许多系统都没有设计一个良好架构的原因,因为它们的开发团队起初都很小,不需要设计一些上层建筑来限制某些事情。

  • 如果一个软件系统是由五个不同的团队合作开发的,而每个团队各自都有七个开发人员的话,不将系统划分成定义清晰的组件和可靠稳定的接口,开发工作就没法继续推进。通常,如果忽略其他因素,该系统的架构会逐渐演变成五个组件,一个组件对应一个团队。

这种一个组件对应一个团队的架构不太可能是该系统在部署、运行以及维护方面的最优方案。但不管怎样,如果研发团队只受开发进度来驱动的话,他们的架构设计最终一定会倾向于这个方向。

2. 部署(Deployment)

为了让开发成为有效的工作,软件系统就必须是可部署的。在通常情况下,个系统的部署成本越高,可用性就越低。因此,实现一键式的轻松部署应该是我们设计软件架构的一个目标。

3. 运行(Operation)

软件架构对系统运行的影响远不及它对开发、部署和维护的影响。几乎任何运行问题都可以通过增加硬件的方式来解决,这避免了软件架构的重新设计。

设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然,简化他们对于系统的理解,这将为整个系统的开发与维护提供很大的帮助。

4. 维护(Maintenance)

在软件系统的所有方面中,维护所需的成本是最高的。满足永不停歇的新功能需求,以及修改层出不穷的系统缺陷这些工作将会占去绝大部分的人力资源。
系统维护的主要成本集中在**“探秘”和“风险”**这两件事上。其中,“探秘(spelunking) ”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险(risk) ”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。
我们可以通过精雕细琢的架构设计极大地降低这两项成本。通过将系统切分为组件,并使用稳定的接口将组件隔离,我们可以将未来新功能的添加方式明确出来,并大幅度地降低在修改过程中对系统其他部分造成伤害的可能性,

5. 保持可选项

正如之前章节中所说的,软件有行为价值与架构价值两种价值。这其中的第二种价值又比第一种更重要,因为它正是软件之所以“软”的原因。
软件被发明出来就是因为我们需要一种灵活和便捷的方式来改变机器的行为。而软件的灵活性则取决于系统的整体状况、组件的布置以及组件之间的连接方式。
我们让软件维持“软”性的方法就是尽可能长时间地保留尽可能多的可选项。
那么到底哪些选项是我们应该保留的们就是那些无关紧要的细节设计。
基本上,所有的软件系统都可以降解为策略与细节这两种主要元素。

  • 策略体现的是软件中所有的业务规则与操作过程,因此它是系统真正的价值所在。
  • 细节则是指那些让操作该系统的人、其他系统以及程序员们与策略进行交互,但是又不会影响到策略本身的行为。它们包括I/O设备、数据库、Web系统、服务器、框架、交互协议等。

软件架构师的目标是创建一种系统形态,该形态会以策略为最基本的元素,并让细节与策略脱离关系,以允许在具体决策过程中推迟或延迟与细节相关的内容。

如果在开发高层策略时有意地让自己摆脱具体细节的纠缠,我们就可以将与具体实现相关的细节决策推迟或延后,因为越到项目的后期,我们就拥有越多的信息来做出合理的决策。

这样做还可以让我们有机会做不同的尝试。例如。如果我们现在手里有部分与数据库无关的高层策略,那么我们就可以用不同的数据库来做实验,以检验该系统与不同数据库之间的适应性和性能。
我们保留这些可选项的时间越长,实验的机会也就越多。而实验做得越多,我们做决策的时候就能拥有越充足的信息。
如果其他人已经替我们做出了决策常一个优秀的软件架构师会假装这些决策还没有确定,并尽可能长时间地让系统有推迟或修改这些决策的能力

一个优秀的软件架构师应该致力于最大化可选项数量。

6. 小结

优秀的架构师会小心地将软件的高层策略与其底层实现隔离开,让高层策略与实现细节脱钩,使其策略部分完全不需要关心底层细节,当然也不会对这些细节有任何形式的依赖。另外,优秀的架构师所设计的策略应该允许系统尽可能地推迟与实现细节相关的决策,越晚做决策越好。

第十六章:独立性

一个设计良好的软件架构必须支持以下几点:

  • 系统的用例与正常运行。
  • 系统的维护。
  • 系统的开发。
  • 系统的部署。

1. 用例

一个系统的架构必须能支持其自身的设计意图。也就是说,如果某系统是一个购物车应用,那么该系统的架构就必须非常直观地支持这类应用可能会涉及的所有用例。

一个设计良好的架构在行为上对系统最重要的作用就是明确和显式地反映系统设计意图的行为,使其在架构层面上可见。

2. 运行

架构在支持系统运行方面扮演着更实际的角色。如果某个系统每秒要处理100 000个用户,该系统的架构就必须能支持这种级别的吞吐量和响应时间。同样的,如果某个系统要在毫秒级的时间内完成对大数据仓库的查询,那么该系统的架构也必须能支持这类操作。

3. 开发

系统的架构在支持开发环境方面当然扮演着重要的角色。
一个由多个不同目标的团队协作开发的系统必须具有相应的软件架构。这样,这些团队才可以各自独立地完成工作,不会彼此干扰。这就需要恰当地将系统切分为一系列隔离良好、可独立开发的组件。然后才能将这些组件分配给不同的团队,各自独立开发。

4. 部署

一个系统的架构在其部署的便捷性方面起到的作用也是非常大的。
设计目标一定是实现“立刻部署”。一个设计良好的架构通常不会依赖于成堆的脚本与配置文件,也不需要用户手动创建一堆“有严格要求”的目录与文件。总而言之,一个设计良好的软件架构可以让系统在构建完成之后立刻就能部署。

5. 保留可选项

一个设计良好的架构应该充分地权衡以上所述的所有关注点,然后尽可能地形成一个可以同时满足所有需求的组件结构。
要实现这种平衡是很困难的。主要问题是,我们在大部分时间里是无法预知系统的所有用例的。事实上我们想要达到的目标本身就是模糊多变的。
一个设计良好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出必要的变更。

6. 按层解耦

从用例的角度来看,架构师的目标是让系统结构支持其所需要的所有用例。但是问题恰恰是我们无法预知全部的用例。好在架构师应该还是知道整个系统的基本设计意图的。架构师可以通过采用单一职责原则(SRP)和共同闭包原则(CCP),以及既定的系统设计意图来隔离那些变更原因不同的部分,集成变更原因相同的部分。

7. 开发的独立性

我们进行架构设计的第三个目标是支持系统的开发。很显然,当系统组件之间被高度解耦之后,开发团队之间的干扰就大大减少了。
只要系统按照其水平分层和用例进行了恰当的解耦,整个系统的架构就可以支持多团队开发,不管团队组织形式是分功能开发、分组件开发、分层开发,还是按照别的什么变量分工都可以。

8. 部署的独立性

当系统按用例和水平分层的解耦之后,会给系统的部署带来极大的灵活性。

9. 解耦模式

按水平分层和用例解耦一个系统有很多种方式。例如,我们可以在源码层次上解耦、二进制层次上解耦(部署),也可以在执行单元层次上解耦(服务)。

  • 源码层次:我们可以控制源代码模块之间的依赖关系,以此来实现一个模块的变更不会导致其他模块也需要变更或重新编译(例如Ruby Gem)。
  • 部署层次:我们可以控制部署单元(譬如jar文件、DLL、共享库等)之间的依赖关系,以此来实现一个模块的变更不会导致其他模块的重新构建和部
    在这种模式下,大部分组件可能还是依然运行在同一个地址空间内,通过彼此的函数调用通信。
  • 服务层次:我们可以将组件间的依赖关系降低到数据结构级别,然后仅通过网络数据包来进行通信。

通常,我会倾向于将系统的解耦推行到某种一旦有需要就可以随时转变为服务的程度即可,让整个程序尽量长时间地保持单体结构,以便给未来留下可选项。

一个设计良好的架构应该能允许一个系统从单体结构开始,以单一文件的形式部署,然后逐渐成长为一组相互独立的可部署单元,甚至是独立的服务或者微服务。最后还能随着情况的变化,允许系统逐渐回退到单体结构。

10. 小结

一个系统所适用的解耦模式可能会随着时间而变化,优秀的架构师应该能预见这一点,并且做出相应的对策。

第十七章:划分边界

软件架构设计本身就是一门划分边界的艺术。

  • 边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
  • 其中有一些边界是在项目初期一甚至在编写代码之前——就已经划分好,而其他的边界则是后来才划分的。
  • 在项目初期划分这些边界的目的是方便我们尽量将一些决策延后进行,并且确保未来这些决策不会对系统的核心业务逻辑产生干扰。

架构师们所追求的目标是最大限度地降低构建和维护一个系统所需的人力资源。一个系统最消耗人力资源的是系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。

1. 过早且不成熟决策

过早且不成熟决策——那些与系统的业务需求(也就是用例)无关的决策。

这部分决策包括我们要采用的框架、数据库、Web服务器、工具库、依赖注入等。

  • 在一个设计良好的系统架构中,这些细节性的决策都应该是辅助性的,可以被推迟的。
  • 一个设计良好的系统架构不应该依赖于这些细节,而应该尽可能地推迟这些细节性的决策,并致力于将这种推迟所产生的影响降到最低。

通过划清边界避免过早且不成熟的决策:
通过划清边界,我们可以推迟和延后一些细节性的决策,这最终会为我们节省大量的时间、避免大量的问题。这就是一个设计良好的架构所应该带来的助益。

2. 边界划分

边界线应该画在那些不相关的事情中间。

  • GUI与业务逻辑无关,所以两者之间应该有一条边界线。
  • 数据库与GUI无关,这两者之间也应该有一条边界线。
  • 数据库又与业务逻辑无关,所以两者之间也应该有一条边界线。

2.1 数据库与业务实例

如下图, BusinessRules是通过DatabaseInterface 来加载和保存数据的。而DatabaseAccess则负责实现该接口,以及其与实际Database的交互。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q0Ismoqy-1656690422204)(CFB83190D69943DF98219727FBB1AEDC)]

请注意,DatabaseAccess类的那两个对外的箭头。这两个箭头都指向了远离DatabaseAccess类的方向,这意味着它们所指向的两个类都不知道DatabaseAccess类的存在。

下面让我们把抽象层次拉高一点,看一下包含多个业务逻辑类的组件与包含数据库及其访问类的组件之间是什么关系。

《架构整洁之道》读书笔记(下)

4. 插件式架构的好处

将系统设计为插件式架构,就等于构建起了一面变更无法逾越的防火墙。

例如:只要GUI是以插件形式插入系统的业务逻辑中的,那么GUI这边所发生的变更就不会影响系统的业务逻辑。

  • 一个系统的GUI与业务逻辑的变更原因、变更速率显然是不同的,所以二者中间应该有一条边界线。
  • 一个系统的业务逻辑与依赖注入框架之间的变更原因和变更速度也会不同,它们之间也应该画边界线。

这其实就是单一职责原则(SRP)的具体实现,SRP的作用就是告诉我们应该在哪里画边界线。

5. 小结

为了在软件架构中画边界线,我们需要先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,而另一部分则是与核心业务逻辑无关但负责提供必要功能的插件。然后通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件。
其实,这也是一种对依赖反转原则(DIP)和稳定抽象原则(SAP)的具体应用,依赖箭头应该由底层具体实现细节指向高层抽象的方向

第十八章:边界剖析

一个系统的架构是由一系列软件组件以及它们之间的边界共同定义的。

1. 跨边界调用

跨边界调用指的是边界线一侧的函数调用另一侧的函数,并同时传递数据的行为。
构造合理的跨边界调用需要我们对源码中的依赖关系进行合理管控。

需要管控源码中的依赖关系原因:

  • 当一个模块的源码发生变更时,其他模块的源码也可能会随之发生变更或重新编译,并需要重新部署。
  • 边界划分,就是指在这些模块之间建立这种针对变更的防火墙。

2. 令人生畏的单体结构

最常见的架构边界通常并没有一个固定的物理形式,它们只是对同一个进程、同一个地址空间内的函数和数据进行了某种划分。这种划分称之为源码层次上的解耦模式。
但从部署的角度来看,这一切到最后都产生了一个单独的可执行文件——这就是所谓的单体结构。
横跨型变更处理

在单体结构中,我们可以运用多态来反转依赖关系。自律式的组件划分仍然可以极大地帮助整个项目的开发、测试与部署,使不同的团队可以独立开发不同的组件,不会互相干扰。高层组件与低层细节之间也可以得到良好的隔离,独立演进。

3. 本地进程

对本地进程来说,良好的边界划就意味着高层进程的源码中不应该包含低层进程的名字、物理内存地址或是注册表键名。需要注意的是,系统架构的设计目标是让低层进程成为高层进程的一个插件

4. 服务

一个服务就是一个进程。服务之间的跨边界通信相对于函数调用来说,速度是非常缓慢的,一定要尽可能地控制通信次数。
我们可以在服务层次上使用与本地进程相同的规则。也就是让较低层次服务成为较高层次服务的“插件”。要确保高层服务的源码中没有包含任何与低层服务相关的物理信息。

5. 小结

除单体结构以外,大部分系统都会同时采用多种边界划分策略。一个按照服务层次划分边界的系统也可能会在某一部分采用本地进程的边界划分模式。
事实上,服务经常不过就是一系列互相作用的本地进程的某种外在形式。无论是服务还是本地进程,它们几乎肯定都是由一个或多个源码组件组成的单体结构,或者一组动态链接的可部署组件。
这也意味着一个系统中通常会同时包含高通信量、低延迟的本地架构边界和低通信量、高延迟的服务边界。

第十九章:策略与层次

所有的软件系统都是一组策略语句的集合。计算机程序就是一组仔细描述如何将输入转化为输出的策略语句的集合。

1. 策略

1.1 策略划分

在大多数非小型系统(nontrivial system)中,整体业务策略通常都可以被拆解为多组更小的策略语句。

  • 一部分策略语句专门用于描述计算部分的业务逻辑;
  • 一部分策略语句则负责描述计算报告的格式;
  • 可能还会有一些用于描述如何校验输入数据的策略。

1.2 策略重组

软件架构设计的工作重点之一就是:将策略彼此分离,然后按照变更的方式进行重新分组。

  • 其中变更原因、时间和层次相同的策略应该被分到同一个组件中。
  • 变更原因、时间和层次不同的策略则应该分属于不同的组件。

架构设计的工作常常需要将组件重排组合成为一个有向无环图。图中的每一个节点代表的是一个拥有相同层次策略的组件,每一条单向链接都代表了一种组件之间的依赖关系,它们将不同级别的组件链接起来。

这里提到的依赖关系是源码层次上的、编译期的依赖关系。这在Java语言中就是指import语句。这里的依赖关系都是在编译过程中所必需的。

2. 层次(Level)

“层次”是按照“输入与输出之间的距离”来定义的。

  • 一条策略距离系统的输入/输出越远,它所属的层次就越高。
  • 而直接管理输入/输出的策略在系统中的层次是最低的。

注意:

  • 数据流向和源码中的依赖关系并不总处于同一方向上。(我们希望源码中的依赖关系与其数据流向脱钩,而与组件所在的层次挂钩。)

组件划分原则:

  • 变更原因和变更时间相同的策略应在职责原则(SRP)和开闭原则(CCP)这两个原则的指导下合并为同一组件。

离输入/输出最远的策略——高层策略——一般变更没有那么频繁。即使发生变更,其原因也比低层策略所在的组件更重大。反之,低层策略则很有可能会频繁地进行一些小变更。

策略隔离好处:
通过将策略隔离,并让源码中的依赖方向都统一调整为指向高层策略,可以大幅度降低系统变更所带来的影响。因为一些针对系统低层组件的紧急小修改几乎不会影响系统中更高级、更重要的组件。

3. 小结

本章针对策略涉及:单一职责原则(SRP)、开闭原则(OCP)、共同闭包原则(CCP)、依赖反转原则(DIP)、稳定依赖原则(SDP)以及稳定抽象原则(SAP)。

第二十章:业务逻辑

1. 业务逻辑

应用程序可以划分为业务逻辑和插件两部分。
业务逻辑是程序中那些真正用于赚钱或省钱的业务逻辑与过程,也称为“关键业务逻辑”。
“关键业务逻辑”通常会需要处理一些数据,这些数据称为“关键业务数据”。
关键业务逻辑和关键业务数据是紧密相关的,所以它们很适合被放在同一个对象中处理。我们将这种对象称为“业务实体(Entity)”。

2. 业务实体

业务实体是一种对象,这种对象中包含了一系列用于操作关键数据的业务逻辑。
这些实体对象要么直接包含关键业务数据,要么可以很容易地访问这些数据。
业务实体的接口层则是由那些实现关键业务逻辑、操作关键业务数据的函数组成的。

业务实体中的类:

  • 将软件中具体实现了该关键业务的部分聚合在一起;
  • 将其与自动化系统中我们所构建的其他部分隔离区分。

备注:业务实体这个概念只要求我们将关键业务数据和关键业务逻辑绑定在一个独立的软件模块内。

3. 用例

用例是关于如何操作一个自动化系统的描述,它定义了用户需要提供的输入数据、用户应该得到的输出信息以及产生输出所应该采取的处理步骤。
用例中包含了对如何调用业务实体中的关键业务逻辑的定义。用例控制着业务实体之间的交互方式。
用例并不描述系统与用户之间的接口,它只描述该应用在某些特定情景下的业务逻辑,这些业务逻辑所规范的是用户与业务实体之间的交互方式,它与数据流入/流出系统的方式无关。

业务实体并不会知道是哪个业务用例在控制它们,这也是依赖反转原则(DIP)的另一个应用情景。也就是像业务实体这样的高层概念是无须了解像用例这样的低层概念的。反之,低层的业务用例却需要了解高层的业务实体。

为什么业务实体属于高层概念,而用例属于低层概念呢br> 因为用例描述的是一个特定的应用情景,这样一来,用例必然会更靠近系统的输入和输出。而业务实体是一个可以适用于多个应用情景的一般化概念,相对地离系统的输入和输出更远。所以,用例依赖于业务实体,而业务实体并不依赖于用例。

4. 请求和响应模型

用例会接收输入数据,并产生输出数据。但在一个设计良好的架构中,用例对象通常不应该知道数据展现给用户或者其他组件的方式。

5. 小结

业务逻辑是一个软件系统存在的意义,它们属于核心功能,是系统用来赚钱或省钱的那部分代码,是整个系统中的皇冠明珠。
这些业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代表业务逻辑的代码应该是整个系统的核心,其他低层概念的实现应该以插件形式接入系统中。业务逻辑应该是系统中最独立、复用性最高的代码。

第二十一章:尖叫的软件架构

1. 架构设计的主题

软件的系统架构应该为该系统的用例提供支持。软件系统的架构设计图应该非常明确地凸显该应用程序会有哪些用例。
架构设计不应该是与框架相关的,这件事不应该是基于框架来完成的。对于我们来说,框架只是一个可用的工具和手段,而不是一个架构所规范的内容。

2. 架构设计的核心目标

一个良好的架构设计应该围绕着用例来展开,这样的架构设计可以在脱离框架、工具以及使用环境的情况下完整地描述用例。
良好的架构设计应该尽可能地允许用户推迟和延后决定采用什么框架、数据库、Web服务以及其他与环境相关的工具。同时,良好的架构设计还应该让我们很容易改变这些决定。总之,良好的架构设计应该只关注用例,并能将它们与其他的周边因素隔离。

3. 框架是工具而不是生活信条

框架通常可以是非常强大、非常有用的,使用框架也是有代价的,我们需要懂得权衡如何使用一个框架并持有怀疑的态度审视每一个框架。
我们需要仔细考虑如何能保持对系统用例的关注,避免让框架主导我们的架构设计。

4. 小结

一个系统的架构应该着重于展示系统本身的设计,而并非该系统所使用的框架。

第二十二章:整洁架构

1. 架构类型

  • 六边形架构(Hexagonal Architecture)(也称为端口与适配器架构,Ports andAdpaters);
  • DCI架构;
  • DCI架构;

这些架构具有同一个设计目标:按照不同关注点对软件进行切割。也就是说,这些架构都会将软件切割成不同的层,至少有一层是只包含该软件的业务逻辑的,而用户接口、系统接口则属于其他层。

架构特点:

  • 独立于框架:这些系统的架构并不依赖某个功能丰富的框架之中的某个函数。框架可以被当成工具来使用,但不需要让系统来适应框架。
  • 可被测试:这些系统的业务逻辑可以脱离UI、数据库、Web服务以及其他的外部元素来进行测试。
  • 独立于UI:这些系统的UI变更起来很容易,不需要修改其他的系统部分。例如,我们可以在不修改业务逻辑的前提下将一个系统的UI由Web界面替换成命令行界面。
  • 独立于数据库:我们可以轻易将这些系统使用的Oracle、SQL Server替换成Mongo、BigTable、CouchDB之类的数据库。因为业务逻辑与数据库之间已经完成了解耦。
  • 独立于任何外部机构:这些系统的业务逻辑并不需要知道任何其他外部接口的存在。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bJRrmlg-1656690422206)(B0DB098F10FB475B8A1CA673CB463075)]

优点:

  • 上述设计为未来构建完整的系统架构边界打下了坚实基础。
  • 为了未来将Client与ServiceImpl隔离,必要的依赖反转已经做完了。

存在的问题:

  • 图中的虚线箭头代表了未来有可能很快就会出现的隔离问题。由于没有采用双向反向接口,这部分就只能依赖开发者和架构师的自律性来保证组件持久隔离了。

3. 门户模式

采用门户模式(facadepattern),其架构如图24.2所示。

《架构整洁之道》读书笔记(下)

3. 增加存储并遵循依赖关系规则

假设玩家在游戏中的状态会被保存在某种持久化存储介质中——有可能闪存,也有可能是某种云端存储,或只是本机内存。无论怎样,我们都并不希望游戏引擎了解这些细节。
这里的设计也要合理地遵守依赖关系原则,如下图:

《架构整洁之道》读书笔记(下)

在该图中,虚线框代表的是抽象组件,它们所定义的API通常要交由其上下层的组件来实现。

需要注意的是:GameRules 与 Language 以及 GameRules 与 Data Storage 都是双向边界。

重点看一下这句:如果我们进一步查看GameRules内部,就会发现GameRules组件的代码中使用的Boundary 多态接口是由 Language 组件来实现的;同时还会发现Language组件使用的Boundary多态接口由GameRules代码实现。

5. 数据流向

我们可以去掉所有的具体实现类,只保留API组件来进一步简化上面这张设计图,其简化的结果如图25.4所示。

《架构整洁之道》读书笔记(下)

7. 数据流的分割

GameRules组件:游戏的部分业务逻辑处理的是玩家在地图中的行走。这一部分需要知道游戏中的洞穴如何相连,每个洞穴中有什么物体存在,还要知道如何将玩家从一个洞穴移到另一个洞穴,以及如何触发各种需要玩家处理的事件。
游戏中还有一组更高层次的策略——这些策略负责了解玩家的血量,以及每个事件的后果和影响。高层组件则要管理玩家状态(如图25.6所示),最终该策略将会决定玩家在游戏中的输赢。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0aEy2Tqv-1656690422209)(CB95FF21ABC54C71AC7D6CD9AC0642F4)]
  • 软件与固件集成在一起也属于设计上的反模式(anti-pattern)。符合这种反模式的代码修改起来都会很困难。

  • 软件与固件之间的分割线往往没有代码与硬件之间的分割线那么清晰。为了将边界定义的更清晰,软件与固件之间的边界被称为硬件抽象层(HAL)。

《架构整洁之道》读书笔记(下)

整洁的嵌入式架构会引入操作系统抽象层(OSAL,如图29.6所示),将软件与操作系统分隔开。

《架构整洁之道》读书笔记(下)

定义了OSAL好处:

  • 可以让自己的应用共享一种公用结构;
  • 可以帮助高价值的应用程序实现在目标平台、目标操作系统之外进行测试。

4. 面向接口编程与可替代性

分层架构的理念是基于接口编程的理念来设计的。当模块之间能以接口形式交互时,我们就可以将一个服务替换成另外一个服务。

5. 小结

为了让我们的产品能长期地保持健康,请别让你的代码都变成固件。如果一个系统的代码只能在目标硬件上测试,那么它的开发过程会变得非常艰难。

第三十章:数据库只是实现细节

1. 门把手和整个房屋架构的关系

从系统架构的角度来看,数据库并不重要——它只是一个实现细节,在系统架构中并不占据重要角色。如果就数据库与整个系统架构的关系打个比方,它们之间就好比是门把手和整个房屋架构的关系。

2. 小结

数据的组织结构,数据的模型,都是系统架构中的重要部分,但是从磁盘上存储/读取数据的机制和手段却没那么重要。关系型数据库强制我们将数据存储成表格并且以SQL访问,主要是为了后者。总而言之,数据本身很重要,但数据库系统仅仅是一个实现细节。

第三十一章:Web是实现细节

1. 集中在中央服务器还是分散在各个终端

Web技术被普遍应用以来,全部计算资源集中在中央服务器上,还是将计算资源分散到各个终端上,在过去十几年中来回摇摆了几次。

2. Web只是一种I/O设备

GUI只是一个实现细节。而Web则是GUI的一种,所以也是一个实现细节。作为一名软件架构师,我们需要将这类细节与核心业务逻辑隔离开来。
Web只是一种I/O设备,编写设备无关应用程序是非常重要的。

第三十二章:应用程序框架是实现细节

框架并不等同于系统架构。

1. 架构的目的

框架是解决框架作者以及可能还包括他的亲戚朋友所遇到的问题,而不是使用框架者遇到的问题。

2. 不对等的关系

我们与框架作者之间的关系是非常不对等的。我们要采用某个框架就意味着自己要遵守一大堆约定,但框架作者却完全不需要为我们遵守什么约定。
对框架作者来说,应用程序与自己的框架耦合是没有风险的。作者对框架有绝对的控制权。

3. 风险

  • 框架自身的架构设计很多时候并不是特别正确的。框架本身可能经常违反依赖关系原则。
  • 框架可能会想要我们将框架耦合在最内圈代码中。而我们一旦引入,就再也不会离开该框架了。
  • 框架本身可能朝着我们不需要的方向演进。也许我们会被迫升级到一个并不需要的新版本,甚至会发现自己之前所使用的旧功能突然消失了,或悄悄改变了行为。
  • 未来我们可能会想要切换到一个更新、更好的框架上。

4. 解决方案

  • 我们可以使用框架——但要时刻警惕,别被它拖住。我们应该将框架作为架构最外圈的一个实现细节来使用,不要让它们进入内圈。
  • 如果框架要求我们根据它们的基类来创建派生类,就请不要这样做!我们可以创造一些代理类,同时把这些代理类当作业务逻辑的插件来管理。
  • 不要让框架污染我们的核心代码,应该依据依赖关系原则,将它们当作核心代码的插件来管理。

5. 不得不接受的依赖

有一些框架是避免不了使用的。例如,如果你在用C++,那么STL就是很难避免使用的。但这应该是自己主动选择的结果。

6. 小结

当我们面临框架选择时,尽量不要草率地做出决定。在全身心投入之前,应该首先看看是否可以部分地采用以增加了解。另外,请尽可能长时间地将

来源:聂炳玉

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年6月1日
下一篇 2022年6月2日

相关推荐