【学习笔记】《架构整洁之道》(2)

因为读完感觉虽厚,但讲的核心内容并不难总结,所以这个笔记分两部分。第一部分是我划书的摘录。第二部分是用自己的话,总结一下Bob大叔想传达的思想,当然是我认为的。

摘录

构建组件(粒度)有3个基本原则:

  • REP:复用/发布等同原则
    • 软件复用的最小粒度应等同于其发布的最小粒度。
  • CCP:共同闭包原则
    • 我们应该将那些会同时修改,并且为相同目的而修改的类放到同一个组件中,而将不会同时修改,并且不会为了相同目的而修改的那些类放到不同的组件中。
  • CRP:共同复用原则
    • 不要强迫一个组件的用户依赖他们不需要的东西。
    • CRP原则实际是ISP原则的一个普适版本,ISP原则建议我们不要依赖带有不需要函数的类,而CRP原则则是建议我们不要依赖带有不需要类的组件。上述两条建议可以概括为:不要依赖不需要用到的东西。

组件之间的关系,应遵循3个原则:

  • 无依赖环原则
  • 稳定依赖原则:依赖关系必须要指向更稳定的方向。
  • 稳定抽象原则:一个组件的抽象化程序应该与其稳定性保持一致。

如果一个组件要成为稳定组件,那么它就应该由接口和抽象类组成,以便将来做扩展。

在软件系统的所有方面中,维护所需要的成本是最高的。满足永不停歇的新功能需求,以及修改层出不穷的系统缺陷这些工作将占去绝大部分的人力资源。

基本上,所有的软件系统都可以降解为策略与细节这两种主要元素。策略体现的是软件中所有的业务规则与操作过程,因此它是系统真正的价值所在。

而细节则是指那些让操作该系统的人、其它系统以及程序员们与策略进行交互,但是又不会影响到策略本身的行为。它们包括I/O设备、数据库、Web系统、服务器、框架、交互协议等。

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

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

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

正如之前说的,架构师所追求的目标是最大限度的降低构建和维护一个系统所需的人力资源。那么我们就面要了解一个系统最消耗人力资源的是什么?答案是系统中的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。

那么,怎么样决策会被认为是过早且不成熟的呢?答案是那些决策与系统的业务需求(也就是用例)无关。

一条策略距离系统的输入/输出越远,它所属于的层次越高。例如在一个系统中,逻辑代码是最高层次的,而UI和数据库是更低一级层次的。

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

如一个系统,输入一些字符,加密后输出。包含3个组件, readChar()对应输入, translate ()对应业务逻辑,writeChar()对应输出。下面的程序就构成了不正确的架构

func encrpt() {

while(true)

writeChar(translate(readChar()));

}

上面这个程序的错误就在于,让高层组件中的函数,依赖于低层组件的函数readChar()和writeChar()。更好的架构是,在encrpt组件中,定义Char Reader和Char Writer两个接口,而实现console reader和console writer两个接口。这样,当输入输出策略改变时,就不会影响到加密部分的策略。

什么是业务逻辑?

严格的讲,业务逻辑就是程序中那些真正用于赚钱或省钱的业务逻辑与过程。

关键业务逻辑+关键业务数据(由关键业务逻辑处理的一些数据)就构成了业务实体(entity)

尖叫的架构,PS:也许这个翻译有问题,不够雅。

就是那种你看一眼架构图,就知道它是做什么的。比如是一个零售系统,还是一个记账系统。而不是你看到它,看到的只有Rails, Spring/Hibernate这些。

良好的架构设计应该只关注用例,并能将他们与其它的周边因素隔离,这些因素包括你使用什么数据库存储,使用什么UI展示,使用什么框架开发。

用例指的就是业务逻辑,他与上述周边因素完全无关,而且是独立可测试的。

是不是一定要在组件之间设置严格边界呢?

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

同时,我们也必须要了解如果事先忽略了这些边界,后续添加会有多么困难。架构师总处在一个trade-off当中。

Main函数也是一种组件,它处在最低层(高层是业务核心逻辑),可以把它也看作插件——这个插件负责设置起始状态,配置信息,加载外部资源,最后将控制权转交给应用程序的其它高层组件。

测试代码也是一个组件,也处在低层,可以把测试模块也看作一个插件。我们的架构应该是插件式的。

架构设计的任务就是找到高层策略与低层细节之间的架构边界,同时保证这些边界遵守依赖关系规则。所谓的服务本身只是一种比函数调用成本稍高的,分割应用程序行为的一种形式,与系统架构无关。

也就是说,不是你微服务了,就代表有架构了。

微服务解耦合的谬论。

微服务独立开发部署的谬论。

运送猫咪的难题,这是个非常经典的问题,说明了服务化了,并不代码就有了架构。服务也可以按照SOLID原则来设计,按照组件结构来部署,这样就可以做到添加/删除组件时不影响服务的其它组件。系统架构边界事实上并不落在服务之间,而是穿透所有服务,在服务内部以组件的形式存在。

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

测试代码也是系统的一部分。

测试并不是独立于整个系统之外的,恰恰相反,它们是系统的一个重要组成部分。我们需要精心设计这些测试,才能让它们发挥验证系统稳定性和预防问题复发的作用。

真正制作出一个可复用框架之前,是不知道怎么制作一个可复用框架的。想要制作一个可复用的框架,必须要和几个复用该框架的应用一起开发。

思考

一个好的整洁架构,是分层的插件式架构,策略代码(业务逻辑)处于最高层(多层圈的最核心处),没有任何依赖。它定义了数据库、UI等接口,由其它组件实现这些接口并作为插件挂载上来(DIP)。好处是低层组件修改不会影响核心业务逻辑,低层组件可以随便替换(SRP+OCP+LSP)。

系统架构设计时,应尽可能保留可选项,推迟细节的选择,如数据库/UI/框架是细节,跟具体业务逻辑无关。我们可以在高层策略代码中定义接口,将决策留到最后面选择,如数据是存到SQL还是NoSQL,亦或是存文件。

一个系统不是使用了微服务就自带架构了。典型的出租车调度系统中,要增加猫咪运送的功能,这属于横跨性变更的问题,需要修改所有服务。系统的架构边界其实并不落在服务之间,而是穿透所有服务,在服务的内部以组件的形式存在。

supplier, finder, dispatcher, selector这4个服务并不是系统边界。

这个概念对理解什么是架构的本质很重要。

系统应该设计为可测试的,测试组件属于系统的一部分,它可以以插件形式存在。

猜你喜欢

转载自www.cnblogs.com/gm-201705/p/12897898.html