架构整洁之道系列软件架构师与软件架构

Posted shadowingszy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了架构整洁之道系列软件架构师与软件架构相关的知识,希望对你有一定的参考价值。

最近一直在读《Clean Architecture》这本书,书中对与软件设计与架构的阐述是非常深刻的。因此开了一篇专栏,来记录《Clean Architecture》书中一些优秀的架构设计理念,以及我对这些内容的思考。


一、什么是软件架构师

软件架构师首先必须是程序员,而且得是能力技术强的一线程序员。他们会在自身承接编程任务的同时逐步引导团队向最佳的系统设计方向前进。如果不在一线亲自编码,就体会不到设计的好与坏,就会迷失正确的设计方向。

二、软件架构的目的

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

而在设计过程中,我们需要在保证系统能够正常工作的基础上,尽可能长时间地保留尽可能多的“可选项”,这样才能够更灵活地应对未来的功能变更。

比如说,在早期开发阶段,我们其实不需要过于关心我们要使用哪一种数据库,也不需要过早的引入诸如 DI、ORM 之类的框架。这样做的好处是我们在设计架构时如果能有意地让自己摆脱实现细节的纠缠,我们就能够更灵活地进行决策。

三、业务逻辑

业务逻辑是一个软件系统存在的意义,是最核心的功能,是给我们创造收益的那部分代码。

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

四、划分边界

设计软件架构本身就是一种划分边界的行为,我们需要把一个软件切分为各个模块。而如何合理的划分每个模块的边界,是一个架构师必须要考虑的事情。

划分边界线的原则:边界线应该划在那些不相关的事情中间,比如 GUI 与业务逻辑无关,这两者中间应该要划边界线。

而当我们把系统切割成一个又一个的模块之后,就会发现有一部分模块是系统的核心业务逻辑,另一部分是与核心业务逻辑无关但也提供了必要的功能。而后者我们可以将它们视为一个又一个的插件,然后通过对源代码的修改让这些插件依赖于核心业务组件。这样,我们就能使用插拔插件的方式,来切换很多不同类型的模块,提高系统面对功能变更时的灵活性。

而我们按照这种方式设计出来的系统,通常都会有如下特点:

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

五、举个例子

假设我们要做一款基于文本的冒险游戏,这个游戏的操作是通过玩家输入“上下左右”这样的简单文字命令来完成的。玩家在输入命令之后,计算机就会返回玩家⻆色触发的事件。

现在,假设我们决定保留这种基于文字的交互方式,但是需要将 UI 与游戏业务逻辑之间的耦合解开,以便我们的游戏版本可以在不同地区使用不同的语言。我们该怎么做呢——我们需要让游戏的业务逻辑与 UI 之间用一种与自然语言无关的 API 来进行通信,而由 UI 负责将 API 传递进来的信息转换成合适的自然语言,就像下图这样:

同时,假设玩家在游戏中的状态会被保存到某种持久化存储模块中(比如闪存,又或者是云端存储),但我们不希望游戏引擎了解这些细节,所以,我们仍然需要创建一个 API 来负责游戏的业务逻辑组件与数据存储组件之间的通信。

另外,语言并不是 UI 变更的唯一方向,我们也可能会改变文字的输入输出方式(比如采用命令行,或者信息窗口),这样我们就又要构建一套 API,这样整个系统就更加复杂了。

然后我们将它简化一下,去掉具体实现,就变成下面这样:

可以看到所有模块和箭头共同组成了一个有向无环图,且箭头最终都指向了 Game Rules 这个核心模块,这种设计就是很合理的设计。但实际上,我们很难遇到这种所有数据流都汇聚到同一个组件上的情况。就拿这个 Game Rules 来说,我需要处理玩家的移动逻辑,同时也要处理玩家的血量、攻击力等逻辑。那么这个系统的架构就会变成这样:

再比如,如果我们需要把这个游戏变成一个多人在线的游戏,然后我们规定玩家的逻辑在本地处理,移动逻辑在服务端处理,那系统就变得更复杂了:

所以你看,一个很简单的文字冒险游戏,也能拓展成一个具有相当多模块和边界的复杂程序。

六、总结一下

作为架构师,我们必须要小心审视究竟在什么地方才需要设计架构边界。另外,我们还必须弄清楚完全实现这些边界将会带来多大的成本。同时,我们也必须要了解如果事先忽略了这些边界,后续再添加会有多么困难。

所以作为架构师,我们应该怎么办?这个问题恐怕没有一个通用的答案。

一方面,就像一些很聪明的人多年来一直告诉我们的那样,不应该将未来的需求抽象化,臆想中的需求事实上住往是不存在的,过度的工程设计往往比工程设计不足还要糟糕。

但另一方面,如果我们发现自己在某个位置确实需要设置架构边界,却又没有事先准备的时候,再添加边界所需要的成本和⻛险往往是很高的。

现实就是这样。作为软件架构师,我们必须有一点未卜先知的能力。有时候要依靠猜测,有时需要依赖过往的经验,当然最重要的是要用点脑子。软件架构师必须仔细权衡成本,决定哪里需要设计架构边界,以及这些地方需要的是完整的边界,还是不完全的边界,还是可以忽略的边界。

当出现问题时,我们还需要权衡一下实现这个边界的成本,并拿它与不实现这个边界的成本对比。我们的目标是找到设置边界的优势超过其成本的拐点,那就是实现该边界的最佳时机。

架构整洁之道(架构篇)

只有顺心意,才能逆天命 --猫腻《择天记》

接上文:架构整洁之道(原则篇)

欢迎关注微信公众号“江湖喵的修炼秘籍”

1.什么是软件架构

什么是软件架构?

软件架构的实质就是规划如何将系统切分成组件,安排好组件之间的排列关系,以及组件组件之间的通信方式。

“软件架构师”的工作内容是什么?

软件架构师应该坚持做一线程序员,应该在自身承接编程任务的同时引导团队向一个能够最大化生产力的系统设计方向前进。如果架构师从代码中解放出来,就不能亲身承受因系统设计而带来的麻烦和痛苦

软件架构设计的目标?

软件架构设计的主要目标是支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解,易于修改,方便维护,并且能够轻松部署。

软件架构的终极目标是最大化程序员的生产力,最小化系统的总运营成本。

以下从不同角度看待软件架构的目标:

**开发:**软件架构的作用是便于开发

**部署:**一个系统的部署成本越高,可用性越低,实现一键式的轻松部署是软件架构设计的一个目标。运用微服务架构时,要合理控制服务数量。

**维护:**系统维护成本主要集中在“探秘”和“风险”两件事情上,“探秘”的目的是确认新增功能或者被修复问题的最佳位置和最佳方式;“风险”的目的是确认再进行修改时,可能衍生出的新问题。软件架构设计的目标是降低这两个成本。

**保持可选项:**一个优秀的软件架构师应该致力于最大化的可选项数量。高层策略应该避免固化具体的实现细节,比如选择何种UI,选择哪种数据库等等,应尽可能长的延迟这种决策,保留更多的选择性。

**设备无关性:**程序应该与设备无关。

2.重复

架构师们经常会钻进一个牛角尖——害怕重复。

当然,重复在软件行业里一般来说都是坏事。我们不喜欢重复的代码,当代码真的出现重复时,我们经常会感到作为一个专业人士,自己是有责任减少或消除这种重复的。

但是重复也存在着很多种情况。其中有些是真正的重复,在这种情况下,每个实例上发生的每项变更都必须同时应用到其所有的副本上。重复的情况中也有一些是假的,或者说这种重复只是表面性的。如果有两段看起来重复的代码,它们走的是不同的演进路径,也就是说它们有着不同的变更速率和变更缘由,那么这两段代码就不是真正的重复。等我们几年后再回过头来看,可能会发现这两段代码是非常不一样的了。

当我们按用例垂直切分系统的时候,这样的问题会经常出现。我们经常遇到一些不同的用例为了上述原因被耦合在了一起。不管是因为它们展现形式类似,还是使用了相似的语法、相似的数据库查询/表结构等,总之,我们一定要小心避免陷入对任何重复都要立即消除的应激反应模式中。一定要确保这些消除动作只针对那些真正意义上的重复。

3.划分边界

一个系统中最消耗人力资源的 是系统中存在的耦合。

我们应该在软件架构中划分边界,将系统分割成组件,一部分是系统的核心业务逻辑组件,一部分是与核心业务逻辑无关但是负责提供必要功能的插件。

业务逻辑是什么

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

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

架构设计的任务就是找到高层策略与底层细节之间的架构边界,同时保证这些边界遵守依赖关系规则。

4.尖叫的软件架构

架构!=框架

架构设计不应该是与框架相关的,架构应该基于用例来设计,而不应该基于框架来设计。

良好的架构设计应该可以让人第一眼看出这是个什么系统(架构应该尖叫的告诉你)。

一个良好的架构设计应该围绕着用例展开,这样的架构设计可以在脱离框架、工具以及使用环境下完整地描述用例。这就好像一个住宅建筑设计的首要目的应该是满足住宅的使用需求,而不是确保一定要用砖来构建这个房子。架构师应该花费很多精力来确保该架构的设计在满足用例的需要下,尽可能地允许用户能自由地选择建筑材料(砖头、石料或者木材)。

而且,良好的架构设计应该尽可能地允许用户推迟和延后决定采用什么框架、数据库、Web服务以及其他与环境相关的工具。框架应该是一个可选项,良好的架构设计应该允许用户在项目后期再决定是否采用Rails、Spring、Hibernate、Tomcat、MySQL这些工具。同时,良好的架构设计还应该让我们很容易改变这些决定。总之,良好的架构设计应该只关注用例,并能够将它们与其他的周边因素隔离。

5.整洁架构

软件层次从低到高分别是:

框架与驱动程序 (数据库、Web、用户界面、设备、外部接口) < 接口适配器 ( 控制器、展示器、网关) < 应用级业务逻辑(用例) <系统级业务逻辑(业务实体)

源码中的依赖关系也必须按照这个方向流动,即低层机制指向高层机制。

5.1 业务实体

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

5.2 用例

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

我们既不希望在这一层所发生的的变更影响业务实体,同时也不希望这一层受外部因素(如数据库、UI、常见框架)的影响。用例层应该与它们都保持隔离。

5.3 接口适配器

软件的接口适配器层中通常是一组数据转换器,它们负责将数据从对用例和业务实体而言最方便操作的格式,转化为外部系统(如数据库、Web)最方便操作的格式。例如,这一层应该包含整个MVC框架。展示器、视图、控制器都应该属于接口适配器层。而模型部分则应该由控制器传递给用例,再由用例传回展示器和视图。

同样的,这一层的代码也会负责将数据从对业务实体与用例而言最方便操作的格式,转化为对所采用的持久性框架最方便的格式。从该层再往内的同心圆中,其代码就不应该依赖任何数据库了。

这一层的代码还会负责将来自外部服务的数据转换成系统内用例与业务实体所需的格式。

5.4 框架与驱动程序

最外层的模型层一般是由工具、数据库、Web框架等组成的。在这一层中,我们通常只需要编写一些与内层沟通的黏合性代码。

框架与驱动程序层中包含了所有的实现细节。Web是一个实现细节,数据库也是一个实现细节。我们将这些细节放在最外层,这样就很难影响到其他层了。

6.解耦谬论

谬论:拆分服务最重要的一个好处就是让每个服务之间实现强解耦。

维服务架构中,拆分服务并不意味着这些服务可以彼此独立开发、部署和运维,如果这些服务之间以数据形式或者行为形式相耦合,那么它们的开发、部署和运维也鄙夫彼此协调来进行。如何为了满足一个需求需要多个服务共同修改,则这些服务本质上都是强耦合的,这就是横跨度变更问题。

以上是关于架构整洁之道系列软件架构师与软件架构的主要内容,如果未能解决你的问题,请参考以下文章

架构整洁之道(架构篇)

架构整洁之道-软件架构

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

笔记架构整洁之道

架构整洁之道 7~12章读书笔记

《架构整洁之道》