架构整洁之道 15~29章读书笔记

第5部分 软件架构

第15章 什么是软件架构

软件架构师自身需要是程序员,并且必须一直坚持做一线程序员,绝对不要听从那些说应该让软件架构师从代码中解放出来以专心解决高阶问题的伪建议。

也许软件架构师生产的代码量不是最多的,但是他们必须不停地承接编程任务。如果不亲身承受因系统设计而带来的麻烦,就体会不到设计不佳所带来的痛苦,接着就会逐渐迷失正确的设计方向。

设计软件架构的目的,就是为了在工作中更好地对这些组件进行研发、部署、运行以及维护。

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

软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。

开发(Development)

一个开发起来很困难的软件系统一般不太可能会有一个长久、健康的生命周期,所以系统架构的作用就是要方便其开发团队对它的开发。这意味着,不同的团队结构应该采用不同的架构设计。对于一个只有五个开发人员的小团队来说,他们完全可以非常高效地共同开发一个没有明确定义组件和接口的单体系统(monolithic system)。如果一个软件系统是由五个不同的团队合作开发的,通常,如果忽略其他因素,该系统的架构会逐渐演变成五个组件,一个组件对应一个团队。

部署(Deployment)

一个系统的部署成本越高,可用性就越低。

运行(Operation)

软件架构对系统运行的影响远不及它对开发、部署和维护的影响。几乎任何运行问题都可以通过增加硬件的方式来解决,这避免了软件架构的重新设计。对于一个因架构设计糟糕而效率低下的系统,我们通常只需要增加更多的存储器与服务器,就能够让它圆满地完成任务。另外,硬件也远比人力要便宜,这也是软件架构对系统运行的影响远没有它对开发、部署、维护的影响那么深远的一个原因。

并不是说我们不应该为了让系统能更好地运转而优化软件的架构设计,这样做是应该的,只是基于投入/产出比的考虑,我们的优化重心应该更倾向于系统的开发、部署以及维护。

一个设计良好的软件架构应该能明确地反映该系统在运行时的需求。

是设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然。架构应该起到揭示系统运行过程的作用。具体来说,就是该架构应该将系统中的用例、功能以及该系统的必备行为设置为对开发者可见的一级实体,简化它们对于系统的理解,这将为整个系统的开发与维护提供很大的帮助。

维护(Maintenance)

系统维护的主要成本集中在“探秘”和“风险”这两件事上。其中,“探秘(spelunking)”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险(risk)”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。

保持可选项

软件有行为价值与架构价值两种价值。这其中的第二种价值又比第一种更重要。

我们让软件维持“软”性的方法就是尽可能长时间地保留尽可能多的可选项。那么到底哪些选项是我们应该保留的?它们就是那些无关紧要的细节设计。

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

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

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

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

本章小结

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

第16章 独立性

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

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

用例

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

开发

任何一个组织在设计系统时,往往都会复制出一个与该组织内沟通结构相同的系统。

部署

一个设计良好的架构通常不会依赖于成堆的脚本与配置文件,也不需要用户手动创建一堆“有严格要求”的目录与文件。

按层解耦

从用例的角度来看,架构师的目标是让系统结构支持其所需要的所有用例。

系统可以被解耦成若干个水平分层——UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库等。

用例的解耦

如果我们按照变更原因的不同对系统进行解耦,就可以持续地向系统内添加新的用例,而不会影响旧有的用例。

解耦的模式

基于服务来构建的架构,架构师们通常称之为面向服务的架构(service-oriented architecture)。

开发的独立性

只要系统按照其水平分层和用例进行了恰当的解耦,整个系统的架构就可以支持多团队开发

部署的独立性

按用例和水平分层的解耦也会给系统的部署带来极大的灵活性。如果解耦工作做得好,我们甚至可以在系统运行过程中热切换(hot-swap)其各个分层实现和具体用例。

重复

如果有两段看起来重复的代码,它们走的是不同的演进路径,也就是说它们有着不同的变更速率和变更缘由,那么这两段代码就不是真正的重复。等我们几年后再回过头来看,可能就会发现这两段代码是非常不一样的了。

我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复。

再谈解耦模式

我们可以在源码层次上解耦、二进制层次上解耦(部署),也可以在执行单元层次上解耦(服务)。

源码层次:我们可以控制源代码模块之间的依赖关系,以此来实现一个模块的变更不会导致其他模块也需要变更或重新编译。

部署层次:我们可以控制部署单元(譬如jar文件、DLL、共享库等)之间的依赖关系,以此来实现一个模块的变更不会导致其他模块的重新构建和部署。

服务层次:我们可以将组件间的依赖关系降低到数据结构级别,然后仅通过网络数据包来进行通信。这样系统的每个执行单元在源码层和二进制层都会是一个独立的个体,它们的变更不会影响其他地方(例如,常见的服务或微服务就都是如此的)。

在项目早期很难知道哪种模式是最好的。事实上,随着项目的逐渐成熟,最好的模式可能会发生变化。

另一个解决方案(似乎也是目前最流行的方案)是,默认就采用服务层次的解耦。这种做法的问题主要在于它的成本很高,并且是在鼓励粗粒度的解耦。服务层次解耦的另一个问题是不仅系统资源成本高昂,而且研发成本更高。

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

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

  • 系统最初的组件隔离措施都是做在源码层次上的,这样的解耦可能在整个项目的生命周期里已经足够了。然而,如果部署和开发方面有更高的需求出现,那么将某些组件解耦到部署单元层次就可能够了,起码能撑上一阵。
  • 随着系统在开发、部署、运行各方面所面临的问题持续增加,我们应该挑选一下可以将哪些可部署单元转化为服务,并且逐渐将系统向这个方向转变。
  • 随着时间的流逝,系统的运维需求可能又会降低。之前需要进行服务层次解耦的系统可能现在只需要进行部署层次或源码层次的解耦就够了。

本章小结

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

第17章 划分边界

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

一个系统最消耗人力资源的是什么?答案是系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。

怎样的决策会被认为是过早且不成熟的呢?答案是那些决策与系统的业务需求(也就是用例)无关。这部分决策包括我们要采用的框架、数据库、Web服务器、工具库、依赖注入等。在一个设计良好的系统架构中,这些细节性的决策都应该是辅助性的,可以被推迟的。

我们在业务逻辑和数据库之间画了一条边界线。这条线有效地防止了业务逻辑对数据库产生依赖,它只能访问简单的数据访问方法。这个决策使我们将与数据库选型和实现的决策推迟了超过一年。

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

应在何时、何处画这些线

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

。数据库应该是业务逻辑间接使用的一个工具。业务逻辑并不需要了解数据库的表结构、查询语言或其他任何数据库内部的实现细节。业务逻辑唯一需要知道的,就是有一组可以用来查询和保存数据的函数。

界应该穿过继承关系,在DatabaseInterface之下
架构整洁之道 15~29章读书笔记_第1张图片

输入和输出怎么办

一个非常重要的原则,即I/O是无关紧要的。

不重要的组件依赖于较为重要的组件。

插件式架构

软件开发技术发展的历史就是一个如何想方设法方便地增加插件,从而构建一个可扩展、可维护的系统架构的故事。

边界线也应该沿着系统的变更轴来画。也就是说,位于边界线两侧的组件应该以不同原因、不同速率变化着。

一个系统的GUI与业务逻辑的变更原因、变更速率显然是不同的,所以二者中间应该有一条边界线。这其实就是单一职责原则(SRP)的具体实现,SRP的作用就是告诉我们应该在哪里画边界线。

本章小结

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

第18章 边界剖析

跨边界调用

跨边界调用指的是边界线一侧的函数调用另一侧的函数,并同时传递数据的行为。

为什么需要管控源码中的依赖关系呢?因为当一个模块的源码发生变更时,其他模块的源码也可能会随之发生变更或重新编译,并需要重新部署。所谓划分边界,就是指在这些模块之间建立这种针对变更的防火墙。

部署层次的组件

系统架构最常见的物理边界形式:动态链接库。

与单体结构类似,按部署层次解耦的组件之间的跨边界调用也只是普通的函数调用,成本很低。

线程

线程既不属于架构边界,也不属于部署单元,它们仅仅是一种管理并调度程序执行的方式。

本地进程

系统架构还有一个更明显的物理边界形式,那就是本地进程。

本地进程之间的隔离策略也与单体结构、二进制组件基本相同,其源码中的依赖关系跨越架构边界的方向是一致的,始终指向更高层次的组件。

本地进程之间的隔离策略也与单体结构、二进制组件基本相同,其源码中的依赖关系跨越架构边界的方向是一致的,始终指向更高层次的组件。

对本地进程来说,这就意味着高层进程的源码中不应该包含低层进程的名字、物理内存地址或是注册表键名。请读者务必要记住,该系统架构的设计目标是让低层进程成为高层进程的一个插件。

本地进程之间的跨边界通信需要用到系统调用、数据的编码和解码,以及进程间的上下文切换,成本相对来说会更高一些,所以这里需要谨慎地控制通信的次数。

服务

系统架构中最强的边界形式就是服务。

服务之间的跨边界通信相对于函数调用来说,速度是非常缓慢的。因此我们在划分架构边界时,一定要尽可能地控制通信次数。

第19章 策略与层次

软件架构设计的工作重点之一就是,将这些策略彼此分离,然后将它们按照变更的方式进行重新分组。其中变更原因、时间和层次相同的策略应该被分到同一个组件中。反之,变更原因、时间和层次不同的策略则应该分属于不同的组件。

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

一般来说,低层组件被设计为依赖于高层组件。

层次

我们希望源码中的依赖关系与其数据流向脱钩,而与组件所在的层次挂钩。

通过将策略隔离,并让源码中的依赖方向都统一调整为指向高层策略,我们可以大幅度降低系统变更所带来的影响。

低层组件应该成为高层组件的插件。

第20章 业务逻辑

业务逻辑就是程序中那些真正用于赚钱或省钱的业务逻辑与过程。更严格地讲,无论这些业务逻辑是在计算机上实现的,还是人工执行的,它们在省钱/赚钱上的作用都是一样的。例如,银行要对借贷收取N%利息这个逻辑就是银行获取收入方面的一条业务逻辑。

关键业务逻辑和关键业务数据是紧密相关的,所以它们很适合被放在同一个对象中处理。我们将这种对象称为“业务实体(Entity)”。

业务实体

业务实体这个概念中应该只有业务逻辑,没有别的。

业务实体不一定非要用面向对象编程语言的类来实现。业务实体这个概念只要求我们将关键业务数据和关键业务逻辑绑定在一个独立的软件模块内。

用例

并不是所有的业务逻辑都是一个纯粹的业务实体

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

本章小结

业务逻辑是一个软件系统存在的意义,它们属于核心功能,是系统用来赚钱或省钱的那部分代码。

业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代表业务逻辑的代码应该是整个系统的核心,其他低层概念的实现应该以插件形式接入系统中。业务逻辑应该是系统中最独立、复用性最高的代码。

第21章 尖叫的软件架构

架构设计的主题

架构设计不是(或者说不应该是)与框架相关的,这件事不应该是基于框架来完成的。框架只是一个可用的工具和手段,而不是一个架构所规范的内容。

架构设计的核心目标

一个良好的架构设计应该围绕着用例来展开,这样的架构设计可以在脱离框架、工具以及使用环境的情况下完整地描述用例。这就好像一个住宅建筑设计的首要目标应该是满足住宅的使用需求,而不是确保一定要用砖来构建这个房子。

良好的架构设计应该尽可能地允许用户推迟和延后决定采用什么框架、数据库、Web服务以及其他与环境相关的工具。

Web只是一种交付手段

一个系统应该尽量保持它与交付方式之间的无关性。在不更改基础架构设计的情况下,我们应该可以将一个应用程序交付成命令行程序、Web程序、富客户端程序、Web服务程序等任何一种形式的程序。

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

可测试的架构设计

如果系统架构的所有设计都是围绕着用例来展开的,并且在使用框架的问题上保持谨慎的态度,那么我们就应该可以在不依赖任何框架的情况下针对这些用例进行单元测试。另外,我们在运行测试的时候不应该运行Web服务,也不应该需要连接数据库。我们测试的应该只是一个简单的业务实体对象,没有任何与框架、数据库相关的依赖关系。

本章小结

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

新来的程序员应该先了解该系统的用例,而非系统的交付方式。

第22章 整洁架构

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

  • 独立于框架
  • 可被测试
  • 独立于UI
  • 独立于数据库
  • 独立于任何外部机构(接口)

架构整洁之道 15~29章读书笔记_第2张图片

依赖关系规则

源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。

任何属于内层圆中的代码都不应该牵涉外层圆中的代码,尤其是内层圆中的代码不应该引用外层圆中代码所声明的名字,包括函数、类、变量以及一切其他有命名的软件实体。

业务实体

业务实体这一层中封装的是整个系统的关键业务逻辑,一个业务实体既可以是一个带有方法的对象,也可以是一组数据结构和函数的集合。

用例

用例层中通常包含的是特定应用场景下的业务逻辑,这里面封装并实现了整个系统的所有用例。这些用例引导了数据在业务实体之间的流入/流出,并指挥着业务实体利用其中的关键业务逻辑来实现用例的设计目标。

只有四层吗

并没有某个规则约定一个系统的架构有且只能有四层。然而,这其中的依赖关系原则是不变的。源码层面的依赖关系一定要指向同心圆的内侧。层次越往内,其抽象和策略的层次越高,同时软件的抽象程度就越高,其包含的高层策略就越多。最内层的圆中包含的是最通用、最高层的策略,最外层的圆包含的是最具体的实现细节。

跨边界传输的对象应该有一个独立、简单的数据结构。总之,不要投机取巧地直接传递业务实体或数据库记录对象。同时,这些传递的数据结构中也不应该存在违反依赖规则的依赖关系。

第23章 展示器和谦卑对象

谦卑对象模式

谦卑对象模式最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离。其设计思路非常简单,就是将这两类行为拆分成两组模块或类。其中一组模块被称为谦卑(Humble)组,包含了系统中所有难以测试的行为,而这些行为已经被简化到不能再简化了。另一组模块则包含了所有不属于谦卑对象的行为。

GUI通常是很难进行单元测试的,因为让计算机自行检视屏幕内容,并检查指定元素是否出现是非常难的事情。然而,GUI中的大部分行为实际上是很容易被测试的。这时候,我们就可以利用谦卑对象模式将GUI的这两种行为拆分成展示器与视图两部分。

展示器与视图

展示器则是可测试的对象。展示器的工作是负责从应用程序中接收数据,然后按视图的需要将这些数据格式化,以便视图将其呈现在屏幕上。

应用程序所能控制的、要在屏幕上显示的一切东西,都应该在视图模型中以字符串、布尔值或枚举值的形式存在。然后,视图部分除了加载视图模型所需要的值,不应该再做任何其他事情。因此,我们才能说视图是谦卑对象。

测试与架构

强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一。

数据映射器

ORM更应该被称为“数据映射器”,因为它们只是将数据从关系型数据库加载到了对应的数据结构中。这样的ORM系统应该属于系统架构中的哪一层呢?当然是数据库层。ORM其实就是在数据库和数据库网关接口之间构建了另一种谦卑对象的边界。

本章小结

在每个系统架构的边界处,都有可能发现谦卑对象模式的存在。因为跨边界的通信肯定需要用到某种简单的数据结构,而边界会自然而然地将系统分割成难以测试的部分与容易测试的部分,所以通过在系统的边界处运用谦卑对象模式,我们可以大幅地提高整个系统的可测试性。

第24章 不完全边界

YAGNI原则(“You Aren’t Going to Need It”,意即“不要预测未来的需要”)

省掉最后一步

构建不完全边界的一种方式就是在将系统分割成一系列可以独立编译、独立部署的组件之后,再把它们构建成一个组件。换句话说,在将系统中所有的接口、用于输入/输出的数据格式等每一件事都设置好之后,仍选择将它们统一编译和部署为一个组件。

门户模式(facade pattern)

在这种模式下,我们连依赖反转的工作都可以省了。这里的边界将只能由Facade类来定义。

单向边界

使用临时占位结构,方便将来替换成完整架构边界的更简单的结构。

架构整洁之道 15~29章读书笔记_第3张图片
这个结构采用了传统的策略模式(strategy pattern),其Client使用的是一个由ServiceImpl类实现的ServiceBoundary接口。为了未来将Client与ServiceImpl隔离,必要的依赖反转已经做完了。

本章小结

介绍了三种不完全地实现架构边界的简单方法:

  • 省掉最后一步
  • 门户模式
  • 单向边界

架构师的职责之一就是预判未来哪里有可能会需要设置架构边界,并决定应该以完全形式还是不完全形式来实现它们。

第25章 层次与边界

API的定义和维护都是由使用方来负责的,而非实现方。

作为架构师,我们必须要小心审视究竟在什么地方才需要设计架构边界。另外,我们还必须弄清楚完全实现这些边界将会带来多大的成本。

这就是YAGNI原则:“You aren’t going to need it”,臆想中的需求事实上往往是不存在的。这是一句饱含智慧的建议,因为过度的工程设计往往比工程设计不足还要糟糕。但另一方面,如果我们发现自己在某个位置确实需要设置一个架构边界,却又没有事先准备的时候,再添加边界所需要的成本和风险往往是很高的。

我们不能在项目开始时就决定好哪里需要设计边界,哪里不需要。相反,架构师必须持续观察系统的演进,时刻注意哪里可能需要设计边界,然后仔细观察这些地方会由于不存在边界而出现哪些问题。

我们的目标是找到设置边界的优势超过其成本的拐点,那就是实现该边界的最佳时机。持之以恒,一刻也不能放松。

第26章 Main组件

最细节化的部分

Main组件是系统中最细节化的部分——也就是底层的策略,它是整个系统的初始点。在整个系统中,除了操作系统不会再有其他组件依赖于它了。Main组件的任务是创建所有的工厂类、策略类以及其他的全局设施,并最终将系统的控制权转交给最高抽象层的代码来处理。

Main组件是整个系统中细节信息最多的组件。

我们在这里的重点是要说明Main组件是整个系统中的一个底层模块,它处于整洁架构的最外圈,主要负责为系统加载所有必要的信息,然后再将控制权转交回系统的高层组件。

本章小结

Main组件也可以被视为应用程序的一个插件——这个插件负责设置起始状态、配置信息、加载外部资源,最后将控制权转交给应用程序的其他高层组件。另外,由于Main组件能以插件形式存在于系统中,因此我们可以为一个系统设计多个Main组件,让它们各自对应于不同的配置。

第27章 服务:宏观与微观

面向服务的架构

有人说“只要使用了服务,就等于有了一套架构”,这显然是完全错误的。架构设计的任务就是找到高层策略与低层细节之间的架构边界,同时保证这些边界遵守依赖关系规则。所谓的服务本身只是一种比函数调用方式成本稍高的,分割应用程序行为的一种形式,与系统架构无关。

并不是说所有的服务都应该具有系统架构上的意义。有时候,用服务这种形式来隔离不同平台或进程中的程序行为这件事本身就很重要——不管它们是否遵守依赖关系规则。我们只是认为,服务本身并不能完全代表系统架构。

服务所带来的好处

任何形式的共享数据行为都会导致强耦合。

服务的接口与普通的函数接口相比,并没有比后者更正式、更严谨。

大型系统一样可以采用单体模式,或者组件模式来构建,不一定非得服务化。因此服务化并不是构建大型系统的唯一选择。

拆分服务并不意味着这些服务可以彼此独立开发、部署和运维。如果这些服务之间以数据形式或者行为形式相耦合,那么它们的开发、部署和运维也必须彼此协调来进行。

横跨型变更(cross-cutting concern)问题,它是所有的软件系统都要面对的问题,无论服务化还是非服务化的。按功能切分服务的架构方式,在跨系统的功能变更时是最脆弱的。

基于组件的服务

服务并不一定必须是小型的单体程序。服务也可以按照SOLID原则来设计,按照组件结构来部署,这样就可以做到在添加/删除组件时不影响服务中的其他组件。

横跨型变更

系统的架构边界事实上并不落在服务之间,而是穿透所有服务,在服务内部以组件的形式存在。

服务边界并不能代表系统的架构边界,服务内部的组件边界才是。

服务内部的组件的设计必须符合依赖指向规则。

本章小结

虽然服务化可能有助于提升系统的可扩展性和可研发性,但服务本身却并不能代表整个系统的架构设计。系统的架构是由系统内部的架构边界,以及边界之间的依赖关系所定义的,与系统中各组件之间的调用和通信方式无关。

一个服务可能是一个独立组件,以系统架构边界的形式隔开。一个服务也可能由几个组件组成,其中的组件以架构边界的形式互相隔离。

第28章 测试边界

我们可以将测试组件视为系统架构中最外圈的程序。

软件设计的第一条原则,就是不要依赖于多变的东西。

测试专用API应该被授予超级用户权限,允许测试代码可以忽视安全限制,绕过那些成本高昂的资源(例如数据库),强制将系统设置到某种可测试的状态中。

设置测试API是为了将测试部分从应用程序中分离出来。换句话说,这种解耦动作不只是为了分隔测试部分与UI部分,而是要将测试代码的结构与应用程序其他部分的代码结构分开。

应该将测试专用API及其对应的具体实现放置在一个单独的、可独立部署的组件中。

没有按系统组成部分来设计的测试代码,往往是非常脆弱且难以维护的。这种测试最后常常会被抛弃,因为它们终究会出问题。

第29章 整洁的嵌入式架构

“虽然软件本身并不会随时间推移而磨损,但硬件及其固件却会随时间推移而过时,随即也需要对软件做相应改动。”

“虽然软件质量本身并不会随时间推移而损耗,但是未妥善管理的硬件依赖和固件依赖却是软件的头号杀手。”

如果你在代码中嵌入了SQL或者是代码中引入了对某个平台的依赖的话,其实就是在写固件代码。

不要再写固件代码了,让我们的代码活得更久一点!

“程序适用测试”测试

软件构建过程中的三个阶段:

  • 1.“先让代码工作起来”——如果代码不能工作,就不能产生价值。
  • 2.“然后再试图将它变好”——通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。
  • 3.“最后再试着让它运行得更快”——按照性能提升的“需求”来重构代码。

整洁的嵌入式架构就是可测试的嵌入式架构

  • 分层
  • 硬件是实现细节。HAL的存在是为了给它上层的软件提供服务,HAL的API应该按照这些软件的需要来量身定做。
  • 不要向HAL(硬件抽象层)的用户暴露硬件细节。
  • 处理器是实现细节
  • 操作系统是实现细节。整洁的嵌入式架构会引入操作系统抽象层(OSAL),将软件与操作系统分隔开。
    架构整洁之道 15~29章读书笔记_第4张图片

你可能感兴趣的:(读书笔记,架构)