总结:此书可读。

初读第一遍时觉得内容泛泛,没有吸收到什么东西。第二遍细读时,才有些了解了自己认为的“架构”和作者论述的架构之间的区别,纠正了许多原有错误的认知。

很多时候写代码,都是凭借以往的经验在脑袋中构建出一个大致的结构,按这种结构去实现各个模块,最后组装成一个整体。只是根据以往的经验认为这样可行,具体为什么可行说不上来,很多东西都可以在此书找到理论依据。

本书精华应当是整洁架构那章,围绕业务核心逻辑以及用例分层构建整个系统,内层独立于外层,外层依赖内层,外层以插件形式接入系统。

一、设计与架构究竟是什么

  1. 设计与架构没有区别,设计信息与架构信息不可分割,共同构成整个软件系统;
  2. 软件架构的终极目标,用最小的人力成本来满足构建和维护系统的需求;
  3. 不要低估设计良好整洁的代码的重要性;
  4. 以后不可能重构,重构只能从此刻开始;
  5. 容忍糟糕的代码不可能加快进度,当前不可能,以后更不可能;
  6. 要想跑的快,先要跑的稳;
  7. 抛弃重写不一定是解决问题的方法,可能是重蹈覆辙,跳入另外一个泥潭;
  8. 过度自信的重构设计可能会陷入和原项目一样的困局;

二、两个价值维度

  1. 行为价值,也就是软件系统的行为,表现在实现的业务功能;
  2. 架构价值,软件的灵活性,易于修改;
  3. 业务认为行为价值更大,这是没有考虑未来可能的需求变更;
  4. 开发应当认为架构价值更大,可修改、易修改的程序才是有价值的;
  5. 业务部门没有能力评估系统架构的重要程度;
  6. 平衡系统架构的重要性与功能的紧急程度是开发人员的职责;
  7. 关注系统的整体架构,创建功能易实现、易修改、易扩展的软件架构;
  8. 忽视软件架构,最终可能会导致系统无法维护;

三、编程范式总览

  1. 结构化编程,限制和规范了程序控制权的直接转移,限制goto语句;
  2. 面向对象编程,限制和规范了程序控制的间接转移,限制函数指针;
  3. 函数式编程,限制和规范了程序中的赋值,限制赋值语句;
  4. 多态是跨越架构边界的手段;
  5. 函数式编程是限制数据存放位置与访问权限的手段;
  6. 结构化编程是各模块的算法实现基础;
  7. 三种范式对应功能性、组件独立性、数据管理;

四、结构化编程

  1. 用顺序结构、分支结构、循环结构可以构造出任何程序;
  2. 错误的使用goto语句是有害的;
  3. 限制goto的目标不能超出当前函数范围;
  4. 结构化编程范式促使将一段程序降解为一系列可证明的小函数,如果这些小函数是正确的,进而推导整个程序是正确的;
  5. 在架构设计领域功能性降解拆分仍然是最佳实践之一;

五、面向对象编程

  1. 封装不是面向对象编程独有的,C语言可以通过结构前置声明使用达到数据封装的目的,相反的面向对象中由于变量必须在头文件中声明导致削弱了封装;
  2. 继承不是面向对象开创,C语言可能通过两个结构体前面部分结构定义相同达到继承的功能,但面向对象确实在数据结构的伪装上提供了更大的便利;
  3. 多态是函数指针的一种应用,面向对象让多态变得更安全、更便于使用;
  4. 面向对象约束了函数指针,其实是约束了程序的间接控制权的转移;
  5. 程序应当与设备无关;
  6. 依赖反转,A->B->C,A依赖B,B依赖C,如果把B为基类,C继承自B,则B和C的依赖关系发生了反转,变成了C依赖从B继承来的接口;
  7. 利用多态可以对源码级别的依赖关系进行反转;
  8. 基类提供统一的接口,派生类分别实现这些接口,做到接口不变的插件形式组件;
  9. 面向对象编程是以多态为手段对源码中的依赖关系进行控制,构建出插件式架构,使得高层策略性组件与底层实现性组件相分离,底层组件可以被编译成插件,实现独立于高层组件的开发和部署;

六、函数式编程

  1. 依赖于λ演算;
  2. 函数式编程语言中的变量初始化后值是不可改变的;
  3. 应该将状态可变和状态不可变的部分隔离成单独的组件,用合适的机制保护可变量;
  4. 应当着力于将大部分处理逻辑归于不可变组件,可变组件逻辑越少越好;
  5. 事件溯源,只存储事务记录,不存储具体状态,就是只有增加和读取,没有删除和修改操作,源代码管理使用这种方式工作;

七、SRP单一职责原则

  1. SRP原则,任何一个软件模块都应该只对某一类行为者负责;
  2. 将服务不同行为者的代码进行切分;

八、OCP开闭原则

  1. 良好的设计应该在不需要修改的前提下易于扩展;
  2. 如果A不想被B的修改所影响,那么就应该让B依赖于A;
  3. 将系统划分为一系列组件,将组件间的依赖关系按层次结构组织,使得高阶组件不会因低阶组件被修改而受到影响;

九、LSP里氏替换原则

  1. 以基类作为参数的接口,不依赖于任何一个派生类,派生类可替换基类;
  2. LSP应该被应用于软件架构层面,一旦违背了可替换性,系统架构就不得不添加大量复杂的应对机制;

十、ISP接口隔离原则

  1. 一般情况下,任何层次的软件设计如果依赖于不需要的东西,都会是有害的;

十一、DIP依赖反转原则

  1. 灵活的系统,在源代码层次应该多用抽象类型,而不是具体实现;
  2. 接口比实现更稳定;
  3. 尽量在不修改接口的情况下为软件增加新的功能;
  4. 应在代码中多使用抽象接口,尽量避免使用那些多变的具体实现类,通常可以使用抽象工厂设计模式;
  5. 不要在具体实现类上创建派生类;
  6. 不要覆盖包含具体实现的函数;
  7. 应避免在代码中写入与任何具体实现相关的名字,或者是其他容易变动的事物的名字;
  8. 利用抽象工厂模式管理依赖关系,整个系统划分为抽象接口与其具体实现;
  9. 源代码的依赖方向永远是控制流方向的反转,这就是DIP被称为依赖反转的原则;
  10. 通常main组件用于具体实现组件;

十二、组件

  1. 组件是软件的部署单元,是可以独立完成部署的最小实体;
  2. 设计良好的组件应当永远保持独立部署的特性;
  3. .jar/.dll/.exe都是组件的一种表现形式;

十三、组件聚合

  1. 复用/发布等同原则,软件复用的最小粒度应等同于其发布的最小粒度;
  2. 组件中的类与模块必须是彼此紧密相连的,不能由一组毫无关联的类和模块组成,他们之间应该有一个共同的主题或大方向;
  3. 共同闭包原则,将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,将不会同时修改,并且不会为了相同目的而修改的那些类放到不同组件中;
  4. 一个组件不应该同时存在多个变更的原因;
  5. 如果程序必须进行某些变更,这些变更最好体现在同一个组件中;
  6. 将由于相同原因而修改,并且需要同时修改的东西放在一起,将由于不同原因而修改,并且不同时修改的东西分开;
  7. 共同复用原则,不要强迫一个组件的用户依赖他们不需要的东西;
  8. 将经常共同复用的类和模块放在同一个组件中;
  9. 不是紧密相连的类不应该被放在同一个组件里;
  10. 不要依赖不需要用到的东西;
  11. 组件结构设计应该根据项目的开发时间和成熟度不断变动;
  12. 组件的构成安排应随着项目重心的不同,以及研发性与复用性的不同而演化;

十四、组件耦合

  1. 组件依赖关系图中不应该出现环;
  2. 将项目划分为一系列可单独发布的组件;
  3. 严格控制组件之间的依赖关系,绝对不允许存在循环依赖关系;
  4. 可以通过创捷接口类,通过继承接口达到依赖反转的目的,打破循环依赖;
  5. 可以通过创建新的组件,把两个组件相互依赖的类放入新的组件,打破循环依赖;
  6. 必须持续的监控项目中的循环依赖关系,一旦循环依赖出现,必须以某种方式消除;
  7. 避免频繁变更的组件影响到稳定的组件;
  8. 组件依赖关系必须要随着项目的逻辑设计一起扩张和演进;
  9. 依赖关系必须要指向更稳定的方向;
  10. 带有越多入向依赖关系的组件是稳定的,也就是被越多组件依赖的组件是稳定的;
  11. 不依赖其他组件的组件是稳定的,称为“独立”组件;
  12. 不稳定的组件位于顶层,稳定的组件位于底层;
  13. 如果一个稳定的组件需要依赖一个不稳定的组件,则应当创建一个新的组件提供接口,在不稳定的组件中实现该接口,稳定的组件只依赖接口组件;
  14. 一个组件的抽象化程度应该与其稳定性保持一致;
  15. 稳定的组件应该由接口和抽象类组成;
  16. 依赖关系应该指向更抽象的方向;
  17. 被太多其他组件依赖的组件难以修改,过少被其他组件依赖的组件又没有太大作用,应当保持在一个适当范围;

十五、什么是软件架构

  1. 软件架构师是能力最强的一群程序员,引导整个团队向一个能够最大化生产力的系统设计方向前进;
  2. 软件架构师需要持续承接编程任务,不过不亲身承受因系统设计而带来的麻烦,就体会不到设计不佳带来的痛苦,接着就会逐渐迷失正确的设计方向;
  3. 软件架构实质就是规划如何将系统划分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式;
  4. 软件架构的目的是为了在工作中更好地对组件进行研发、部署、运行以及维护;
  5. 如果想设计一个便于推进各项工作的系统,其策略就是要在设计中尽可能长时间地保留尽可能多的可选项;
  6. 软件架构的目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护、并且能轻松部署;
  7. 软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本;
  8. 不同的团队应该采用不同的架构设计,与团队人员规模相关;
  9. 系统部署成本越高,可用性就越低,一键部署应该是设计软件架构的一个目标;
  10. 应该在项目的早期优先考虑部署问题;
  11. 设计良好的架构应该可以使开发人员对系统的运行一目了然;
  12. 在软件系统的所有方面,维护所需的成本是最高的;
  13. 通过将系统切分为组件,并使用稳定的接口将组件隔离,可方便后续维护;
  14. 在开发高层策略时有意地摆脱具体细节,将于具体实现相关的细节推迟,因为越到项目后期,就拥有越多的信息来做出合理的决策;
  15. 一个优秀的软件架构师应该致力于最大化可选项数量;
  16. 设备无关性是一个很好的例子,操作设备的接口统一,后续的实现细节推迟到各个设备自身,体现了高层策略与底层实现细节的分离;
  17. 良好的设计应当将软件的高层策略与底层实现细节分离,使得策略部分不需要关心底层细节,也不会对底层细节有任何形式的依赖;
  18. 良好的设计应当尽可能地推迟与实现细节相关的决策,越晚做出决策越好;

十六、独立性

  1. 软件的架构必须能支持其自身的审计意图,为其使用案例提供支持;
  2. 架构应该在组件之间做一些适当的隔离,同时不强制规定组件之间的交互方式,可以随时根据需求变更;
  3. 设计良好的架构部署时不依赖不依赖于成堆脚本和配置文件;
  4. 设计良好的架构可以让系统在构建完成后立刻技能部署;
  5. 需要通过正确划分、隔离组件实现,开发一些组件,用于黏合整个系统,正确的启动、连接并监视每个组件;
  6. 设计良好的架构应该通过保留可选性的方式,让系统在任何情况下都能方便地做出必须要的变更;
  7. 系统可被解耦成若干个水平分层,按层解耦;
  8. 按使用案例把多个分层做垂直切片,对用例进行解耦;
  9. 面向服务的架构,被隔离的组件运行在不同的服务器上,提供独立的服务,通过某种网络进行通信;
  10. 一个良好的架构总是要为将来多留一些可选项;
  11. 架构设计的目标之一是支持系统多团队独立开发;
  12. 系统按照水平层和用例进行了恰当的解耦,整个系统架构就可以支持多团队开发;
  13. 系统组件解耦做的好可以为部署带来极大的灵活性;
  14. 如果系统解耦做的足够好,甚至可以在系统运行过程进行热切其各个分层实现和具体用例;
  15. 代码重复有真正的重复,有表面上的重复,需要严格区分;
  16. 源码层次解耦,控制源代码模块之间的依赖关系,以此来实现一个模块的变更不会导致其他模块也需要变更或重新编译;
  17. 部署层次解耦,控制部署单元之间的依赖关系,以此来实现一个模块的变更不会导致其他模块重新构建和部署;
  18. 服务层次解耦,将组件间的依赖关系降低到数据结构级别,仅通过网络数据包通信;
  19. 设计良好的架构应该能允许一个系统从单体结构开始,以单一文件的形式部署,然后逐渐成长为一组相互独立的可部署单元,甚至是独立的服务或微服务,最后还能随着情况的变化,允许系统逐渐回退到单体结构;
  20. 系统所使用的解耦模式可能会随着时间而变化,优秀的架构师应该能预见这一点并且做出相应的对策;

十七、划分边界

  1. 软件架构设计是一门划分边界的艺术,边界的作用将软件分割成各种元素,以便约束边界两侧之间的依赖关系;
  2. 系统中最消耗人力资源的是系统中存在的耦合,尤其是过早做出的不成熟的决策导致的耦合;
  3. 过早决策与系统业务需求无关的是不成熟的决策,比如要采用的框架、数据库、Web服务器、工具库、依赖注入等;
  4. 设计良好的架构应该不依赖于细节,尽可能推迟细节性决策,并致力于将这种推迟所产生的影响降到最低;
  5. 架构应该是依赖于业务,由业务的改变逐步演化而来,不应该是开始就制定出的完美的架构;
  6. 良好的架构应该通过划清边界,可以推迟和延后一些细节性的决策;
  7. I/O是无关紧要的,GUI对业务逻辑来说一点都不重要,GUI依赖于业务逻辑;
  8. 软件开发技术的发展历史就是一个如何方便地增加插件,从而构建一个可扩展、可维护的系统架构的故事;
  9. 系统的核心业务逻辑必须和其他组件隔离,保持独立,其他组件要么可以去掉,要么可以有多种实现方式;
  10. 将系统设计为插件式架构,就等于构建了一面变更无法逾越的防火墙;
  11. 将系统分割成组件,一部分是核心业务逻辑组件,另一部分是与核心逻辑无关但负责提供必要功能的插件,让非核心组件依赖于系统核心业务逻辑组件;

十八、边界剖析

  1. 架构由组件及它们之间的边界共同定义,边界有着不同的存在形式;
  2. 运行时跨边界调用指的是边界一侧的函数调用另一侧的函数,并同时传递数据的行为;
  3. 单体结构;
  4. 部署层次组件;
  5. 本地进程;
  6. 系统架构中最强的边界形式就是服务;
  7. 初单体结构外,多数系统都会同时采用多种边界划分策略;
  8. 一个系统中通常会同时包含高通信量、低延迟的本地架构边界和低通信量、高延迟的服务边界;

十九、策略与层次

  1. 设计良好的架构中,一般低层组件被设计为依赖于高层组件;
  2. 距离系统输入/输出越远,所属层次越高,直接管理输入输出的策略在系统中的层次最低;
  3. 高层组件应该被设计为不依赖于低层组件,高层组件应该通过抽象接口的方式调用低层组件,以抽象接口的方式实现高层组件与低层组件解耦;
  4. 低层组件应该成为高层组件的插件,低层组件通过实现高层组件需要的接口,然后可以作为插件被高层组件调用;

二十、业务逻辑

  1. 关键业务逻辑是一项业务的关键部分,不管有没有自动化系统执行这项业务,这些部分都不会改变;
  2. 关键业务数据是无论自动化程序是否存在,都必须存在的数据;
  3. 关键业务逻辑与关键业务数据紧密相关,通常被放在一个业务实体对象中处理;
  4. 业务实体就是将关键业务数据和关键业务逻辑绑定在一个独立的软件模块内;
  5. 业务实体代表了整个业务逻辑,与数据库、GUI、框架等内容无关,业务实体应该只有业务逻辑,没有别的;
  6. 使用案例不描述系统与用户之间的接口,只描述该应用在某些特定情景下的业务逻辑,这些业务逻辑规范的是用户与业务实体之间的交互方式;
  7. 不要在请求/响应模型的数据结构中引用业务实体,即使它们之间有很多相同的数据,但它们存在的意义不一样,随着时间的推移,这两个对象会以不同的原因、不同的速率发生变更;
  8. 业务逻辑是软件系统存在的意义,属于核心功能,是用来赚钱或省钱的那部分代码;
  9. 业务逻辑应该保持纯净,不要掺杂用户界面或所使用的数据库相关的东西;
  10. 理想的情况下,业务逻辑代码应该是整个系统的核心,其他低层组件应该以插件形式接入系统;
  11. 业务逻辑应该是系统中最独立,复用性最高的代码;

二十一、尖叫的软件架构

  1. 软件的系统架构应该为该系统的使用案例提供支持;
  2. 架构设计不应该与框架相关,不应该是基于框架来完成的,框架只是一个可用的工具和手段,而不是架构所规范的内容;
  3. 良好的架构设计应该围绕着使用案例展开,这样的设计可以脱离框架、工具以及使用环境的情况下完整地描述使用案例,就像一个住宅建筑的设计首要目标应该是满足住宅的使用需求,而不是确保一定要用砖来构建房子,良好的架构设计是在满足使用案例需求的情况下,尽可能地允许用户自由地选择建筑材料;
  4. 系统应该尽量保持它与交付方式之间的无关性;
  5. 框架是工具而不是生活信条;
  6. 一定要带着怀疑的态度审视每一个框架,权衡如何使用一个框架,避免让框架主导架构设计;
  7. 应该通过使用案例对象来调度业务实体对象,确保所有测试都不需要依赖框架;
  8. 系统的架构应该着重于展示系统本身的设计,而不是该系统所使用的框架;

二十二、整洁架构

  1. 系统架构应该独立于框架、可被测试、独立于GUI、独立于数据库、独立于任何外部机构;
  2. 整洁架构示意图如下:

C:\Users\Wero\Desktop\CleanArchitecture.jpg

越靠近中心,所属层次越高,外层代表机制,内层代表策略。

源码中的依赖关系必须是外层指向内层,由底层机制指向高层策略。任何内层代码中都不应该牵涉外层代码,尤其是内层圆中的代码不应该引用外层圆中代码所声明的名字,包括函数、类、变量以及一切其他有命名的软件实体。

外层圆中使用的数据格式也不应该被内层圆中的代码所使用,尤其是当数据格式由外层的框架所产生时。总之,不应该让外层圆中发生的任何变更影响到内层圆的代码。

业务实体层(Entities)这一层封装的是整个系统的关键业务逻辑,可以是带有方法的对象,也可以是一组数据结构和函数的集合。业务实体封装了最通用、最高层的业务逻辑,属于系统中最不容易受外界影响而变动的部分。

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

接口适配器层(Controllers、Gateways、Presenters)通常是一组数据转换器,他们负责将数据从对用例和业务实体而言最方便操作的格式,转化为外部系统最方便操作的格式,例如方便持久化的数据格式。也会负责将来自外部服务的数据转换成系统内用例与业务实体所需要的格式。

框架与驱动程序层(Devices、Web、UI、External Interfaces、DB)一般是由工具、数据库、Web框架等组成。在这一层中通常只需要编写一些与内层沟通的黏合性代码。

图中同心圆只是为了说明架构的结构,真正的架构很可能会超过四层。但其中的依赖关系不变,源码层面的依赖关系一定要是外层指向内层。层次越往内,抽象和策略层次越高,软件抽象程度越高,包含的高层策略就越多。最内层包含的是通用的、最高层的策略,最外层的圆包含的是最具体的实现细节。

图中右下侧示范的是架构中跨越边界的情况。控制器、展示器与下一层用例之间的通信过程。用例中的代码不能直接调用展示器,因为内层代码不能引用外层的声明。需要在用例层提供一个接口,并让展示器来负责实现这个接口。

一般会跨越边界的数据在数据结构上都是很简单的。尽量采用一些基本的结构体或简单的可传输数据对象。或者直接通过函数调用的参数来传递数据。跨边界传输的对象应该有一个独立、简单的数据结构。不能直接传递业务实体或数据库记录对象。跨边界传输时,一定要采用内层最方便使用的形式。

所有跨边界的依赖线都应当是指向内层的,遵守架构依赖关系原则;

二十三、展示起和谦卑对象

  1. 谦卑对象,指的是仅用于传递数据而不干预数据的对象,难以测试行为;
  2. GUI可拆分为展示器和视图两部分,视图属于难以测试的谦卑对象,这种对象的代码应该越简单越好,只应负责将数据填充到GUI上,不应该对数据进行任何处理;展示器则是可测试对象,复制从应用程序中接收数据,按视图需要格式化,以便视图将其呈现在屏幕上;
  3. 应用程序所能控制的、要在屏幕上显示的一切东西,都应在视图模型中存在,视图部分只负责加载视图模型需要的值,不做其他任何事情,所以说视图是谦卑对象;
  4. 强大的可测试性是一个架构的设计是否优秀的显著衡量标准之一;
  5. 数据库网关是用例交互器与数据库中间的组件;
  6. 跨边界的通信肯定需要用到某种简单的数据结构,而边界会将系统分割为难以测试和容易测试两部分,通过在边界处运用谦卑对象模式,可以大幅地提高整个系统的可测试性;

二十四、不完全边界

  1. 构建完整的架构边界是一件很消耗成本的事,会涉及大量的前期工作和后期维护工作;
  2. 构建不完全边界的一种方式就是将系统分割成可以独立编译、部署的组件后,再把他们构建成一个组件,省去了组件管理和发布管理方面的工作;
  3. 单向边界,使用临时占位接口,将来可被替换为完整架构边界的更简单的结构;
  4. 门户模式,边界统一定义;
  5. 架构师的职责之一就是预判未来哪里有可能会需要设置架构边界,并决定应该以完全形式还是不完全形式来实现它们;

二十五、层次与边界

  1. 架构边界可以存在于任何地方,必须要小心审视究竟在什么地方才需要设计结构边界;
  2. 过度的设计往往比设计不足还要糟糕;
  3. 架构师必须持续观察系统的演进,时刻注意哪里可能需要设计边界,然后仔细观察这些地方会由于不存在边界而出现哪些问题;
  4. 目标是找到设置边界的优势超过其成本的拐点,那就是实现该边界的最佳时机;

二十六、Main组件

  1. 在所有系统中都至少有一个Main组件,它负责创建、协调、监督其他组件的运转;
  2. Main组件的任务是创建所有的工厂类、策略类以及其他的全局设施,并最终将系统的控制权转交给最高抽象层的代码来处理;
  3. 在整个系统中,应当除了操作系统,不会再有其他组件依赖Main组件;
  4. Main组件是整个系统的一个底层模块,它处于整洁架构的最外圈,主要负责为系统加载所有必要的信息,然后再将控制权转交给系统的高层组件;
  5. Main组件可被视为应用程序的一个插件,负责设置起始状态、配置信息、加载资源,转交控制权给其他高层组件;

二十七、宏观与微观

  1. 架构设计的任务是找到高层策略与低层细节之间的架构边界,同时保证这些边界遵守依赖关系原则;
  2. 服务不等于架构,只是分割应用程序行为的一种形式,与系统架构无关;
  3. 服务不过是一种跨进程/平台边界的函数调用,有些服务具有架构意义,有些则没有;
  4. 服务不能实现解耦合,比如服务之间传递的数据记录中增加一个新字段,每个需要操作这个字段的服务都必须修改,服务之间必须对这条数据的解读达成一致,这些服务是强耦合于这条数据结构,因此服务之间是间接彼此耦合的;
  5. 服务并不意味着可以独立开发部署,如果服务之间以数据形式或者行为形式耦合,它们的开发、部署和运维也必须彼此协调进行;
  6. 服务不一定是单体程序,也可以按照组件来部署,这样可以做到在添加/删除组件时不影响服务中的其他组件;
  7. 系统的架构边界并不落在服务之间,而是穿透所有服务,在服务内部以组件形式存在;
  8. 服务化可能有助于提升系统的可扩展性和可研发性,但服务本身并不能代表整个系统的架构设计。
  9. 系统的架构设计是由系统内部的架构边界,以及边界之间的依赖关系所定义的,与系统中各组件之间的调用和通信方式无关;
  10. 一个服务可能是一个独立的组件,以系统架构边界的形式隔开。也可能由几个组件组成,其中的组件以架构边界的形式互相隔离;
  11. 在某些情况下,客户端和服务端可能会由于耦合的过于紧密而不具备系统架构意义上的隔离性;

二十八、测试边界

  1. 测试代码也是系统的一部分;
  2. 测试也是一种系统组件;
  3. 测试组件位于系统架构最外圈,始终向内依赖,而且系统中没有组件依赖于测试组件;
  4. 测试组件可以独立部署;
  5. 测试组件的存在是为了支持开发过程,而不是运行过程;
  6. 软件设计的第一条原则,就是不要依赖于多变的东西;
  7. 测试专用API,被授予超级用户权限,允许测试代码忽略安全限制,强制将系统设置到某种可测试状态中;
  8. 应该将测试专用API及其对应的具体实现放置在一个单独的、可独立部署的组件中;

二十九、整洁的嵌入式架构

  1. 软件质量本身不会随时间推移而损耗,但是未妥善管理的硬件依赖和固件依赖却是软件的头号杀手;
  2. 固件不是依据存储位置来定义的,而是由其代码的依赖关系,及其随着硬件的演进在变更难度上的变化来定义的;
  3. 先让代码工作,然后再试图将它变得更好,最后再试着让它运行更快;
  4. 在实践中学习正确的工作方法,然后再重写一个更好的版本;
  5. 如果没有采用某种清晰的架构来设计嵌入式系统的代码结构,就会经常面临只能在目标平台上测试代码的难题,这将拖慢项目的开发进度;
  6. 整洁的嵌入式架构就是可测试的嵌入式架构;
  7. 硬件必须与系统其他部分分开;
  8. 硬件是实现细节;
  9. 处理器是实现细节;
  10. 操作系统是实现细节;
  11. 按功能模块、接口编程以及可替代性来划分系统;
  12. 如果系统代码只能在目标硬件上测试,它的开发过程会变得艰难;
  13. 为产品的长期健康,有必要采用一套整洁的嵌入式架构;

三十、数据库只是实现细节

  1. 从系统的架构角度来看,数据库只是一个实现细节;
  2. 数据库只是在硬盘和内存之间相互传输数据的一种手段;
  3. 数据的组织结构、数据的模型、是系统架构中的重要部分,但数据库只是一种数据存储手段,一种实现细节,数据库本身对系统架构而言并不重要;

三十一、Web是实现细节

  1. GUI只是一个实现细节,而Web则是GUI的一种,系统架构需要将细节与核心业务逻辑隔离开;
  2. 在UI和应用程序之间的某一点上,输入数据会被认为达到了一个完整状态,用例就被允许进入执行阶段,用例执行完后,生成的返回数据又继续在UI于应用程序之间传递;
  3. 可以认为用例都是以设备无关的方式在操作I/O设备;

三十二、应用程序框架是实现细节

  1. 框架不等同于系统架构;
  2. 框架设计很多时候并不是特别正确,经常违反依赖关系原则;
  3. 随着产品的成熟,功能要求很可能超出框架所能提供的范围;
  4. 框架可能会朝着我们不需要的方向演进;
  5. 未来可能会想要切换到一个更新、更好的框架上;
  6. 可以使用框架,但要时刻警惕,别被框架束缚,应该将框架作为架构最外圈的一个实现细节来使用,不要让框架进入内圈;
  7. 不要让框架污染核心代码,将它们当作核心代码的插件来管理;
  8. 一旦在项目中引入一个框架,很有可能在整个生命周期都要依赖它,不管后来情形怎么变化,这个决定都很难更改了,不应该草率做出决定;
  9. 尽可能长时间地将框架留在架构边界之外,越久越好;

三十三、案例分析:视频销售网站

三十四、拾遗

  1. 按层封装,水平切分,分层架构无法展现具体的业务领域信息;
  2. 按功能封装,垂直切分,根据相关功能、业务划分;
  3. 端口和适配器,创造一个业务领域代码与具体实现细节(数据库、框架等)隔离的架构;
  4. 按组件封装,组件是在一个执行环境(应用程序)中的、一个干净、良好的接口背后的一系列相关功能的集合;
  5. 如果不考虑具体实现细节,再好的设计也无法长久;

后续

  1. 单靠纯阅读是无法掌握软件设计方法的,实践不可少;
  2. 保证软件质量是每个人的责任,在区分架构好坏的标准上达成共识是一件非常重要的事;

附录A:架构设计考古

  1. 大型的架构设计有时候会导致大厅灾难,避免过度设计,应该根据所遇到问题的规模做出相应的设计;
  2. 在真正制作出来一个可复用框架之前,是不知道怎么制作一个可复用框架的。想要制作一个可复用框架,必须要和几个复用该框架的应用一起开发。