架构整洁之道
这些人渐渐地发现,这个世界上有很多问题就像翘翘板一样,只能要一边,这一边上去了,另一边就下来了。就像要么用空间换时间,要么用时间换空间一样,你很难找到同时满足空间和时间要求的“双利解”。
每当引入一个新的技术来解决一个已有的问题时,这个新的技术就会带来更多的问题,问题就像有一个生命体一样,它们会不断地繁殖和进化。渐渐地,他们发现,问题的多少和系统的复杂度呈正比,而且不仅是线性正比,还可能呈级数正比,此时就越来越难做技术决定。
无论是微观世界的代码,还是宏观层面的架构,无论是三种编程范式还是微服务架构,它们都在解决一个问题——分离控制和逻辑。所谓控制就是对程序流转的与业务逻辑无关的代码或系统的控制,所谓逻辑则是实实在在的业务逻辑,是解决用户问题的逻辑。控制和逻辑构成了整体的软件复杂度,有效地分离控制和逻辑会让你的系统得到最大的简化。
久远的教诲,古老的智慧
识别关注点、隔离责任、保持核心关注点的封闭。
所谓架构就是“用最小的人力成本来满足构建和维护系统需求”的设计行为。
如果又要保证操作原子性又要能精确还原各时刻的状态,有个办法是这样的:只提供CR操作,而不提供完整的CRUD操作。平时只要追加操作记录即可,各时刻的状态永远通过重放之前的操作记录得出,这样就彻底避免了状态的错乱
序言
物理建筑的组织结构必须遵守“受重力”这一自然规律,同时还要符合建筑材料自身的物理特性。
当讨论软件架构时,要特别注意软件项目是具有递归和分形特点的,最终都要由一行行的代码组成。
CPU速度和网络带宽往往在很大程度上决定了系统的性能,而内存和存储空间的大小也会大幅影响代码的设计野心。
一个好的架构,不仅要在某一特定时刻满足软件用户、开发者和所有者的需求,更要在一段时间内持续满足他们的后续需求。
走快的唯一方法是先走好。
软件架构的规则其实就是排列组合代码块的规则。
概述
做一个好的软件架构师所需要的自律和专注程度可能会让大部分程序员始料未及,更别提软件架构师这个职业本身的社会认同感与人们投身其中的热情了。
采用好的软件架构可以大大节省软件项目构建与维护的人力成本。让每次变更都短小简单,易于实施,并且避免缺陷,用最小的成本,最大程度地满足功能性和灵活性的要求。
某个系统因为其组件错综复杂,相互耦合紧密,而导致不管多么小的改动都需要数周的恶战才能完成。又或是某个系统中到处充满了腐朽的设计和连篇累牍的恶心代码,处处都是障碍。再或者,你有没有见过哪个系统的设计如此之差,让整个团队的士气低落,用户天天痛苦,项目经理们手足无措?你有没有见过某个软件系统因其架构腐朽不堪,而导致团队流失,部门解散,甚至公司倒闭?作为一名程序员,你在编程时体会过那种生不如死的感觉吗?
第1章 设计与架构究竟是什么
底层设计信息和顶层架构设计共同组成了整个房屋的架构文档。
软件设计也是如此。底层设计细节和高层架构信息是不可分割的。它们组合在一起,共同定义了整个软件系统,缺一不可。所谓的底层和高层本身就是一系列决策组成的连续体,并没有清晰的分界线。
软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求。
工程师的大部分时间都消耗在对现有系统的修修补补上,而不是真正完成实际的新功能。
无论是从短期还是长期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢。
要想跑得快,先要跑得稳。综上所述,管理层扭转局面的唯一选择就是扭转开发者的观念,让他们从过度自信的兔子模式转变回来,为自己构建的乱麻系统负起责任来。
过度自信只会使得重构设计陷入和原项目一样的困局中。
不管怎么看,研发团队最好的选择是清晰地认识并避开工程师们过度自信的特点,开始认真地对待自己的代码架构,对其质量负责。
第2章 两个价值维度
对于每个软件系统,我们都可以通过行为和架构两个维度来体现它的实际价值。
大部分程序员认为这就是他们的全部工作。他们的工作是且仅是:按照需求文档编写代码,并且修复任何Bug。这真是大错特错。
软件系统必须保持灵活。软件发明的目的,就是让我们可以以一种灵活的方式来改变机器的工作行为
如果系统的架构设计偏向某种特定的“形状”,那么新的变更就会越来越难以实施。所以,好的系统架构设计应该尽可能做到与“形状”无关。
但研发人员还忘了一点,那就是业务部门原本就是没有能力评估系统架构的重要程度的,这本来就应该是研发人员自己的工作职责!所以,平衡系统架构的重要性与功能的紧急程度这件事,是软件研发人员自己的职责。
第3章 编程范式总览
结构化编程、面向对象编程以及函数式编程。
结构化编程对程序控制权的直接转移进行了限制和规范。
面向对象编程对程序控制权的间接转移进行了限制和规范。
函数式编程对程序中的赋值进行了限制和规范。
每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。
这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。
第4章 结构化编程
程序员可以用代码将一些已证明可用的结构串联起来,只要自行证明这些额外代码是正确的,就可以推导出整个程序的正确性。
Bohm和Jocopini证明了人们可以用顺序结构、分支结构、循环结构这三种结构构造出任何程序。
科学理论和科学定律的特点:它们可以被证伪,但是没有办法被证明。
第5章 面向对象编程
面向对象编程其实是对程序间接控制权的转移进行了约束。
插件式架构就是为了支持这种IO不相关性而发明的,它几乎在随后的所有操作系统中都有应用。
通过这种方法,软件架构师可以完全控制采用了面向对象这种编程方式的系统中所有的源代码依赖关系,而不再受到系统控制流的限制。不管哪个模块调用或者被调用,软件架构师都可以随意更改源代码依赖关系。
业务逻辑组件就可以独立于用户界面和数据库来进行部署了,我们对用户界面或者数据库的修改将不会对业务逻辑产生任何影响,这些组件都可以被分别、独立地部署。
当某个组件的源代码需要修改时,仅仅需要重新部署该组件,不需要更改其他组件,这就是独立部署能力。
面向对象编程就是以多态为手段来对源代码中的依赖关系进行控制的能力,这种能力让软件架构师可以构建出某种插件式架构,让高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署。
第6章 函数式编程
所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或者并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。
这就是事件溯源,在这种体系下,我们只存储事务记录,不存储具体状态。当需要具体状态时,我们只要从头开始计算所有的事务即可。
设计原则
一般情况下,我们为软件构建中层结构的主要目标如下:使软件可容忍被改动。使软件更容易被理解。构建可在多个软件系统中复用的组件。
SOLID原则应该直接紧贴于具体的代码逻辑之上,这些原则是用来帮助我们定义软件架构中的组件和模块的。
如果软件系统想要更容易被改变,那么其设计就必须允许新增代码来修改系统行为,而非只能靠修改原来的代码。
该设计原则指出高层策略性的代码不应该依赖实现底层细节的代码,恰恰相反,那些实现底层细节的代码应该依赖高层策略性的代码。
第7章 SRP:单一职责原则
任何一个软件模块都应该只对一个用户或系统利益相关者负责。
任何一个软件模块都应该只对某一类行为者负责。
第8章 OCP:开闭原则
一个设计良好的计算机系统应该在不需要修改的前提下就可以轻易被扩展。
一个好的软件架构设计师会努力将旧代码的修改需求量降至最小,甚至为0。
我们可以先将满足不同需求的代码分组(即SRP),然后再来调整这些分组之间的依赖关系(即DIP)。
先将不同的操作划分为类,再将这些类分割为不同的组件。如果A组件不想被B组件上发生的修改所影响,那么就应该让B组件依赖于A组件。
软件架构师可以根据相关函数被修改的原因、修改的方式及修改的时间来对其进行分组隔离,并将这些互相隔离的函数分组整理成组件结构,使得高阶组件不会因低阶组件被修改而受到影响。
OCP是我们进行系统架构设计的主导原则,其主要目标是让系统易于扩展,同时限制其每次被修改所影响的范围。
第9章 LSP:里氏替换原则
可替换性:如果对于每个类型是S的对象o1都存在一个类型为T的对象o2,能使操作T类型的程序P在用o2替换o1时行为保持不变,我们就可以将S称为T的子类型。
LSP逐渐演变成了一种更广泛的、指导接口与其实现方式的设计原则。
第10章 ISP:接口隔离原则
在一般情况下,任何层次的软件设计如果依赖于不需要的东西,都会是有害的。
任何层次的软件设计如果依赖了它并不需要的东西,就会带来意料之外的麻烦。
第11章 DIP:依赖反转原则
如果想要设计一个灵活的系统,在源代码层次的依赖关系中就应该多引用抽象类型,而非具体实现。
如果想要在软件架构设计上追求稳定,就必须多使用稳定的抽象接口,少依赖多变的具体实现。
应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类。
继承关系是所有一切源代码依赖关系中最强的、最难被修改的,所以我们对继承的使用应该格外小心。
不要覆盖包含具体实现的函数。
应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字。
组件:抽象接口与其具体实现。抽象接口组件中包含了应用的所有高阶业务规则,而具体实现组件中则包括了所有这些业务规则所需要做的具体操作及其相关的细节信息。
源代码依赖方向永远是控制流方向的反转——这就是DIP被称为依赖反转原则的原因。
组件构建原则
如果说SOLID原则是用于指导我们如何将砖块砌成墙与房间的,那么组件构建原则就是用来指导我们如何将这些房间组合成房子的。
第12章 组件
组件是软件的部署单元,是整个软件系统在部署过程中可以独立完成部署的最小实体。
设计良好的组件都应该永远保持可被独立部署的特性,这同时也意味着这些组件应该可以被单独开发。
第13章 组件聚合
软件复用的最小粒度应等同于其发布的最小粒度。
REP原则就是指组件中的类与模块必须是彼此紧密相关的。也就是说,一个组件不能由一组毫无关联的类和模块组成,它们之间应该有一个共同的主题或者大方向。
我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。
如果两个类紧密相关,不管是源代码层面还是抽象理念层面,永远都会一起被修改,那么它们就应该被归属为同一个组件。
将某一类变更所涉及的所有类尽量聚合在一处。这样当此类变更出现时,我们就可以最大限度地做到使该类变更只影响到有限的相关组件。将由于相同原因而修改,并且需要同时修改的东西放在一起。将由于不同原因而修改,并且不同时修改的东西分开。
因此,当我们决定要依赖某个组件时,最好是实际需要依赖该组件中的每个类。换句话说,我们希望组件中的所有类是不能拆分的,即不应该出现别人只需要依赖它的某几个类而不需要其他类的情况。
ISP原则是建议我们不要依赖带有不需要的函数的类,而CRP原则则是建议我们不要依赖带有不需要的类的组件。
不要依赖不需要用到的东西。
一般来说,一个软件项目的重心会从该三角区域的右侧开始,先期主要牺牲的是复用性。然后,随着项目逐渐成熟,其他项目会逐渐开始对其产生依赖,项目重心就会逐渐向该三角区域的左侧滑动。换句话说,一个项目在组件结构设计上的重心是根据该项目的开发时间和成熟度不断变动的,我们对组件结构的安排主要与项目开发的进度和它被使用的方式有关,与项目本身功能的关系其实很小。
第14章 组件耦合
组件依赖结构图并不是用来描述应用程序功能的,它更像是应用程序在构建性与维护性方面的一张地图。
组件结构图中的一个重要目标是指导如何隔离频繁的变更。我们不希望那些频繁变更的组件影响到其他本来应该很稳定的组件。
一个组件的抽象化程度应该与其稳定性保持一致。
软件架构
软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
如果想设计一个便于推进各项工作的系统,其策略就是要在设计中尽可能长时间地保留尽可能多的可选项。
软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。
实现一键式的轻松部署应该是我们设计软件架构的一个目标。
软件架构对系统运行的影响远不及它对开发、部署和维护的影响。几乎任何运行问题都可以通过增加硬件的方式来解决,这避免了软件架构的重新设计。
对于一个因架构设计糟糕而效率低下的系统,我们通常只需要增加更多的存储器与服务器,就能够让它圆满地完成任务。另外,硬件也远比人力要便宜,这也是软件架构对系统运行的影响远没有它对开发、部署、维护的影响那么深远的一个原因。
在软件系统的所有方面中,维护所需的成本是最高的。满足永不停歇的新功能需求,以及修改层出不穷的系统缺陷这些工作将会占去绝大部分的人力资源。
系统维护的主要成本集中在“探秘”和“风险”这两件事上。其中,“探秘”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。
软件架构师的目标是创建一种系统形态,该形态会以策略为最基本的元素,并让细节与策略脱离关系,以允许在具体决策过程中推迟或延迟与细节相关的内容。
在开发的早期阶段不应该过早地采用REST模式,因为软件的高层策略应该与外部接口无关。同样的,我们也不应该过早地考虑采用微服务框架、SOA框架等。再说一遍,软件的高层策略压根不应该跟这些有关。
一个优秀的软件架构师应该致力于最大化可选项数量。
第16章 独立性
系统的用例与正常运行。系统的维护。系统的开发。系统的部署。
一个设计良好的架构在行为上对系统最重要的作用就是明确和显式地反映系统设计意图的行为,使其在架构层面上可见。
任何一个组织在设计系统时,往往都会复制出一个与该组织内沟通结构相同的系统。
一个设计良好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出必要的变更。
所以架构师可以通过采用单一职责原则(SRP)和共同闭包原则(CCP),以及既定的系统设计意图来隔离那些变更原因不同的部分,集成变更原因相同的部分。
一个系统可以被解耦成若干个水平分层——UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库等。
如果我们按照变更原因的不同对系统进行解耦,就可以持续地向系统内添加新的用例,而不会影响旧有的用例。如果我们同时对支持这些用例的UI和数据库也进行了分组,那么每个用例使用的就是不同面向的UI与数据库,因此增加新用例就更不太可能会影响旧有的用例了。
只要系统按照其水平分层和用例进行了恰当的解耦,整个系统的架构就可以支持多团队开发,不管团队组织形式是分功能开发、分组件开发、分层开发,还是按照别的什么变量分工都可以。
如果有两段看起来重复的代码,它们走的是不同的演进路径,也就是说它们有着不同的变更速率和变更缘由,那么这两段代码就不是真正的重复。
恐怕这里很可能只是表面性的重复。随着时间推移,这两个用例的屏幕展示功能可能会各自演变,最终很可能完全不同。正是由于这样的原因,我们必须加倍小心地避免让这两个用例复用同一段代码,否则,未来再想将它们分开会面临很大的挑战。
我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复。
通常,我会倾向于将系统的解耦推行到某种一旦有需要就可以随时转变为服务的程度即可,让整个程序尽量长时间地保持单体结构,以便给未来留下可选项。
一个设计良好的架构应该能允许一个系统从单体结构开始,以单一文件的形式部署,然后逐渐成长为一组相互独立的可部署单元,甚至是独立的服务或者微服务。最后还能随着情况的变化,允许系统逐渐回退到单体结构。
第17章 划分边界
软件架构设计本身就是一门划分边界的艺术。边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
那么我们就需要了解一个系统最消耗人力资源的是什么?答案是系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。
简单来说,通过划清边界,我们可以推迟和延后一些细节性的决策,这最终会为我们节省大量的时间、避免大量的问题。这就是一个设计良好的架构所应该带来的助益。
大部分人都已经习惯性地认为数据库是与业务逻辑不可分割的了,有些人甚至认为,数据库相关逻辑部分本身就是业务逻辑的具体体现。
数据库应该是业务逻辑间接使用的一个工具。业务逻辑并不需要了解数据库的表结构、查询语言或其他任何数据库内部的实现细节。业务逻辑唯一需要知道的,就是有一组可以用来查询和保存数据的函数。这样一来,我们才可以将数据库隐藏在接口后面。
I/O是无关紧要的;界面对模型——也就是业务逻辑来说——一点都不重要。
软件开发技术发展的历史就是一个如何想方设法方便地增加插件,从而构建一个可扩展、可维护的系统架构的故事。系统的核心业务逻辑必须和其他组件隔离,保持独立,而这些其他组件要么是可以去掉的,要么是有多种实现的
这其实就是单一职责原则(SRP)的具体实现,SRP的作用就是告诉我们应该在哪里画边界线。
为了在软件架构中画边界线,我们需要先将系统分割成组件,其中一部分是系统的核心业务逻辑组件,而另一部分则是与核心业务逻辑无关但负责提供必要功能的插件。然后通过对源代码的修改,让这些非核心组件依赖于系统的核心业务逻辑组件。
第18章 边界剖析
构造合理的跨边界调用需要我们对源码中的依赖关系进行合理管控。
所谓划分边界,就是指在这些模块之间建立这种针对变更的防火墙。
一般来说,现有的内存保护机制会使这些进程无法共享其内存,但它们通常可以用某种独立的内存区域来实现共享。
第19章 策略与层次
软件架构设计的工作重点之一就是,将这些策略彼此分离,然后将它们按照变更的方式进行重新分组。其中变更原因、时间和层次相同的策略应该被分到同一个组件中。反之,变更原因、时间和层次不同的策略则应该分属于不同的组件。
低层组件被设计为依赖于高层组件。我们对“层次”是严格按照“输入与输出之间的距离”来定义的。
一条策略距离系统的输入/输出越远,它所属的层次就越高。而直接管理输入/输出的策略在系统中的层次是最低的。
离输入/输出最远的策略——高层策略——一般变更没有那么频繁。即使发生变更,其原因也比低层策略所在的组件更重大。反之,低层策略则很有可能会频繁地进行一些小变更。
第20章 业务逻辑
关键业务逻辑和关键业务数据是紧密相关的,所以它们很适合被放在同一个对象中处理。我们将这种对象称为“业务实体”。
业务实体这个概念只要求我们将关键业务数据和关键业务逻辑绑定在一个独立的软件模块内。
用例本质上就是关于如何操作一个自动化系统的描述,它定义了用户需要提供的输入数据、用户应该得到的输出信息以及产生输出所应该采取的处理步骤。
用例并不描述系统与用户之间的接口,它只描述该应用在某些特定情景下的业务逻辑,这些业务逻辑所规范的是用户与业务实体之间的交互方式,它与数据流入/流出系统的方式无关。
因为用例描述的是一个特定的应用情景,这样一来,用例必然会更靠近系统的输入和输出。而业务实体是一个可以适用于多个应用情景的一般化概念,相对地离系统的输入和输出更远。所以,用例依赖于业务实体,而业务实体并不依赖于用例。
这些业务逻辑应该保持纯净,不要掺杂用户界面或者所使用的数据库相关的东西。在理想情况下,这部分代表业务逻辑的代码应该是整个系统的核心,其他低层概念的实现应该以插件形式接入系统中。业务逻辑应该是系统中最独立、复用性最高的代码。
第21章 尖叫的软件架构
软件的系统架构应该为该系统的用例提供支持。这就像住宅和图书馆的建筑计划满篇都在非常明显地凸显这些建筑的用例一样,软件系统的架构设计图也应该非常明确地凸显该应用程序会有哪些用例。
一个良好的架构设计应该围绕着用例来展开,这样的架构设计可以在脱离框架、工具以及使用环境的情况下完整地描述用例。
我们一定要带着怀疑的态度审视每一个框架。是的,采用框架可能会很有帮助,但采用它们的成本呢?我们一定要懂得权衡如何使用一个框架,如何保护自己。无论如何,我们需要仔细考虑如何能保持对系统用例的关注,避免让框架主导我们的架构设计。
一个系统的架构应该着重于展示系统本身的设计,而并非该系统所使用的框架。如果我们要构建的是一个医疗系统,新来的程序员第一次看到其源码时就应该知道这是一个医疗系统。新来的程序员应该先了解该系统的用例,而非系统的交付方式。他们可能会走过来问你:“我看到了一些看起来像是模型的代码——但它们的视图和控制器在哪里?”
第22章 整洁架构
它们都具有同一个设计目标:按照不同关注点对软件进行切割。也就是说,这些架构都会将软件切割成不同的层,至少有一层是只包含该软件的业务逻辑的,而用户接口、系统接口则属于其他层。
可被测试:这些系统的业务逻辑可以脱离UI、数据库、Web服务以及其他的外部元素来进行测试。
源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。
业务实体这一层中封装的是整个系统的关键业务逻辑,一个业务实体既可以是一个带有方法的对象,也可以是一组数据结构和函数的集合。无论如何,只要它能被系统中的其他不同应用复用就可以。
软件的接口适配器层中通常是一组数据转换器,它们负责将数据从对用例和业务实体而言最方便操作的格式,转化成外部系统最方便操作的格式。
源码层面的依赖关系一定要指向同心圆的内侧。层次越往内,其抽象和策略的层次越高,同时软件的抽象程度就越高,其包含的高层策略就越多。最内层的圆中包含的是最通用、最高层的策略,最外层的圆包含的是最具体的实现细节。
总之,不要投机取巧地直接传递业务实体或数据库记录对象。同时,这些传递的数据结构中也不应该存在违反依赖规则的依赖关系。所有跨边界的依赖线都是指向内的,这很好地遵守了架构的依赖关系规则。
第23章 展示器和谦卑对象
谦卑对象模式最初的设计目的是帮助单元测试的编写者区分容易测试的行为与难以测试的行为,并将它们隔离。
视图部分属于难以测试的谦卑对象。这种对象的代码通常应该越简单越好,它只应负责将数据填充到GUI上,而不应该对数据进行任何处理。
强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一。
对于用例交互器与数据库中间的组件,我们通常称之为数据库网关。
因为跨边界的通信肯定需要用到某种简单的数据结构,而边界会自然而然地将系统分割成难以测试的部分与容易测试的部分,所以通过在系统的边界处运用谦卑对象模式,我们可以大幅地提高整个系统的可测试性。
第24章 不完全边界
不要预测未来的需要。
构建不完全边界的一种方式就是在将系统分割成一系列可以独立编译、独立部署的组件之后,再把它们构建成一个组件。
第25章 层次与边界
现实就是这样。作为软件架构师,我们必须有一点未卜先知的能力。有时候要依靠猜测——当然还要用点脑子。软件架构师必须仔细权衡成本,决定哪里需要设计架构边界,以及这些地方需要的是完整的边界,还是不完全的边界,还是可以忽略的边界。
第26章 Main组件
Main组件的任务是创建所有的工厂类、策略类以及其他的全局设施,并最终将系统的控制权转交给最高抽象层的代码来处理。
Main组件也可以被视为应用程序的一个插件——这个插件负责设置起始状态、配置信息、加载外部资源,最后将控制权转交给应用程序的其他高层组件。另外,由于Main组件能以插件形式存在于系统中,因此我们可以为一个系统设计多个Main组件,让它们各自对应于不同的配置。
第27章 服务:宏观与微观
架构设计的任务就是找到高层策略与低层细节之间的架构边界,同时保证这些边界遵守依赖关系规则。所谓的服务本身只是一种比函数调用方式成本稍高的,分割应用程序行为的一种形式,与系统架构无关。
任何形式的共享数据行为都会导致强耦合。
如果采用组件化的系统架构,如何解决这个难题呢?通过对SOLID设计原则的仔细考虑,我们应该一开始就设计出一系列多态化的类,以应对将来新功能的扩展需要。
服务边界并不能代表系统的架构边界,服务内部的组件边界才是。
系统的架构是由系统内部的架构边界,以及边界之间的依赖关系所定义的,与系统中各组件之间的调用和通信方式无关。
第28章 测试边界
修改一个通用的系统组件可能会导致成百上千个测试出现问题,我们通常将这类问题称为脆弱的测试问题。
软件设计的第一条原则——不管是为了可测试性还是其他什么东西——是不变的,就是不要依赖于多变的东西。
第29章 整洁的嵌入式架构
“先让代码工作起来”——如果代码不能工作,就不能产生价值。
“然后再试图将它变好”——通过对代码进行重构,让我们自己和其他人更好地理解代码,并能按照需求不断地修改代码。
“最后再试着让它运行得更快”——按照性能提升的“需求”来重构代码。
实现细节
在磁盘上,数据是按照环形轨道存储的。这些轨道又会进一步被划分成一系列扇区,这些扇区的大小通常是4 KB。而每个盘片上都有几百条轨道,整个硬盘可能由十几个盘片组成。如果要从硬盘上读取某一个特定字节,需要将磁头挪到正确的轨道上,等待盘片旋转到正确的位置上,再将整个扇区读入内存中,从内存中查询对应的字节。这些过程当然需要时间,所以硬盘的访问速度一般在毫秒级。
每个系统都有一套索引和安排数据的方式。同时,每种系统最终都会将数据缓存在内存中,方便快速操作。
磁盘真的可以被认为只是一个长期存储数据的、装满字节的大桶。
原文链接:架构整洁之道
:+1: 干货满满
这本书确实不错,我在本站的博客里面,有每一章的详解(我自己的理解),可以一起交流下