第十五章:什么是软件架构
什么是“软件架构”呢?软件架构师的工作内容究竟是什么?这项工作又是什么时候进行的呢?
- 身份:软件架构师自身需要是程序员,并且必须一直坚持做一线程序员。如果不亲身承受因系统设计而带来的麻烦,就体会不到设计不佳所带来的痛苦,接着就会逐渐迷失正确的设计方向。
- 工作:软件架构这项工作的实质就是规划如何将系统切分成组件,并安排好组件之间的排列关系,以及组件之间互相通信的方式。
- 目的:为了在工作中更好地对这些组件进行研发、部署、运行以及维护。
- 目标:支撑软件系统的全生命周期,设计良好的架构可以让系统便于理解、易于修改、方便维护,并且能轻松部署。软件架构的终极目标就是最大化程序员的生产力,同时最小化系统的总运营成本。
如果想设计一个便于推进各项工作的系统,其策略就是要在设计中尽可能长时间地保留尽可能多的可选项。
1. 开发(Development)
一个开发起来很困难的软件系统一般不太可能会有一个长久、健康的生命周期,所以系统架构的作用就是要方便其开发团队对它的开发。
不同的团队结构应该采用不同的架构设计。
- 对于一个只有五个开发人员的小团队来说,他们完全可以非常高效地共同开发一个没有明确定义,组件和接口的单体系统(monolithic system)。
这样的团队可能会发现软件架构在早期开发中反而是一种障碍。这可能就是为什么许多系统都没有设计一个良好架构的原因,因为它们的开发团队起初都很小,不需要设计一些上层建筑来限制某些事情。
- 如果一个软件系统是由五个不同的团队合作开发的,而每个团队各自都有七个开发人员的话,不将系统划分成定义清晰的组件和可靠稳定的接口,开发工作就没法继续推进。通常,如果忽略其他因素,该系统的架构会逐渐演变成五个组件,一个组件对应一个团队。
这种一个组件对应一个团队的架构不太可能是该系统在部署、运行以及维护方面的最优方案。但不管怎样,如果研发团队只受开发进度来驱动的话,他们的架构设计最终一定会倾向于这个方向。
2. 部署(Deployment)
为了让开发成为有效的工作,软件系统就必须是可部署的。在通常情况下,个系统的部署成本越高,可用性就越低。因此,实现一键式的轻松部署应该是我们设计软件架构的一个目标。
3. 运行(Operation)
软件架构对系统运行的影响远不及它对开发、部署和维护的影响。几乎任何运行问题都可以通过增加硬件的方式来解决,这避免了软件架构的重新设计。
设计良好的系统架构应该可以使开发人员对系统的运行过程一目了然,简化他们对于系统的理解,这将为整个系统的开发与维护提供很大的帮助。
4. 维护(Maintenance)
在软件系统的所有方面中,维护所需的成本是最高的。满足永不停歇的新功能需求,以及修改层出不穷的系统缺陷这些工作将会占去绝大部分的人力资源。
系统维护的主要成本集中在**“探秘”和“风险”**这两件事上。其中,“探秘(spelunking) ”的成本主要来自我们对于现有软件系统的挖掘,目的是确定新增功能或被修复问题的最佳位置和最佳方式。而“风险(risk) ”,则是指当我们进行上述修改时,总是有可能衍生出新的问题,这种可能性就是风险成本。
我们可以通过精雕细琢的架构设计极大地降低这两项成本。通过将系统切分为组件,并使用稳定的接口将组件隔离,我们可以将未来新功能的添加方式明确出来,并大幅度地降低在修改过程中对系统其他部分造成伤害的可能性,
5. 保持可选项
正如之前章节中所说的,软件有行为价值与架构价值两种价值。这其中的第二种价值又比第一种更重要,因为它正是软件之所以“软”的原因。
软件被发明出来就是因为我们需要一种灵活和便捷的方式来改变机器的行为。而软件的灵活性则取决于系统的整体状况、组件的布置以及组件之间的连接方式。
我们让软件维持“软”性的方法就是尽可能长时间地保留尽可能多的可选项。
那么到底哪些选项是我们应该保留的?它们就是那些无关紧要的细节设计。
基本上,所有的软件系统都可以降解为策略与细节这两种主要元素。
- 策略体现的是软件中所有的业务规则与操作过程,因此它是系统真正的价值所在。
- 细节则是指那些让操作该系统的人、其他系统以及程序员们与策略进行交互,但是又不会影响到策略本身的行为。它们包括I/O设备、数据库、Web系统、服务器、框架、交互协议等。
软件架构师的目标是创建一种系统形态,该形态会以策略为最基本的元素,并让细节与策略脱离关系,以允许在具体决策过程中推迟或延迟与细节相关的内容。
如果在开发高层策略时有意地让自己摆脱具体细节的纠缠,我们就可以将与具体实现相关的细节决策推迟或延后,因为越到项目的后期,我们就拥有越多的信息来做出合理的决策。
这样做还可以让我们有机会做不同的尝试。例如。如果我们现在手里有部分与数据库无关的高层策略,那么我们就可以用不同的数据库来做实验,以检验该系统与不同数据库之间的适应性和性能。
我们保留这些可选项的时间越长,实验的机会也就越多。而实验做得越多,我们做决策的时候就能拥有越充足的信息。
如果其他人已经替我们做出了决策?通常一个优秀的软件架构师会假装这些决策还没有确定,并尽可能长时间地让系统有推迟或修改这些决策的能力。
一个优秀的软件架构师应该致力于最大化可选项数量。
6. 小结
优秀的架构师会小心地将软件的高层策略与其底层实现隔离开,让高层策略与实现细节脱钩,使其策略部分完全不需要关心底层细节,当然也不会对这些细节有任何形式的依赖。另外,优秀的架构师所设计的策略应该允许系统尽可能地推迟与实现细节相关的决策,越晚做决策越好。
第十六章:独立性
一个设计良好的软件架构必须支持以下几点:
- 系统的用例与正常运行。
- 系统的维护。
- 系统的开发。
- 系统的部署。
1. 用例
一个系统的架构必须能支持其自身的设计意图。也就是说,如果某系统是一个购物车应用,那么该系统的架构就必须非常直观地支持这类应用可能会涉及的所有用例。
一个设计良好的架构在行为上对系统最重要的作用就是明确和显式地反映系统设计意图的行为,使其在架构层面上可见。
2. 运行
架构在支持系统运行方面扮演着更实际的角色。如果某个系统每秒要处理100 000个用户,该系统的架构就必须能支持这种级别的吞吐量和响应时间。同样的,如果某个系统要在毫秒级的时间内完成对大数据仓库的查询,那么该系统的架构也必须能支持这类操作。
3. 开发
系统的架构在支持开发环境方面当然扮演着重要的角色。
一个由多个不同目标的团队协作开发的系统必须具有相应的软件架构。这样,这些团队才可以各自独立地完成工作,不会彼此干扰。这就需要恰当地将系统切分为一系列隔离良好、可独立开发的组件。然后才能将这些组件分配给不同的团队,各自独立开发。
4. 部署
一个系统的架构在其部署的便捷性方面起到的作用也是非常大的。
设计目标一定是实现“立刻部署”。一个设计良好的架构通常不会依赖于成堆的脚本与配置文件,也不需要用户手动创建一堆“有严格要求”的目录与文件。总而言之,一个设计良好的软件架构可以让系统在构建完成之后立刻就能部署。
5. 保留可选项
一个设计良好的架构应该充分地权衡以上所述的所有关注点,然后尽可能地形成一个可以同时满足所有需求的组件结构。
要实现这种平衡是很困难的。主要问题是,我们在大部分时间里是无法预知系统的所有用例的。事实上我们想要达到的目标本身就是模糊多变的。
一个设计良好的架构应该通过保留可选项的方式,让系统在任何情况下都能方便地做出必要的变更。
6. 按层解耦
从用例的角度来看,架构师的目标是让系统结构支持其所需要的所有用例。但是问题恰恰是我们无法预知全部的用例。好在架构师应该还是知道整个系统的基本设计意图的。架构师可以通过采用单一职责原则(SRP)和共同闭包原则(CCP),以及既定的系统设计意图来隔离那些变更原因不同的部分,集成变更原因相同的部分。
7. 开发的独立性
我们进行架构设计的第三个目标是支持系统的开发。很显然,当系统组件之间被高度解耦之后,开发团队之间的干扰就大大减少了。
只要系统按照其水平分层和用例进行了恰当的解耦,整个系统的架构就可以支持多团队开发,不管团队组织形式是分功能开发、分组件开发、分层开发,还是按照别的什么变量分工都可以。
8. 部署的独立性
当系统按用例和水平分层的解耦之后,会给系统的部署带来极大的灵活性。
9. 解耦模式
按水平分层和用例解耦一个系统有很多种方式。例如,我们可以在源码层次上解耦、二进制层次上解耦(部署),也可以在执行单元层次上解耦(服务)。
- 源码层次:我们可以控制源代码模块之间的依赖关系,以此来实现一个模块的变更不会导致其他模块也需要变更或重新编译(例如Ruby Gem)。
- 部署层次:我们可以控制部署单元(譬如jar文件、DLL、共享库等)之间的依赖关系,以此来实现一个模块的变更不会导致其他模块的重新构建和部
在这种模式下,大部分组件可能还是依然运行在同一个地址空间内,通过彼此的函数调用通信。 - 服务层次:我们可以将组件间的依赖关系降低到数据结构级别,然后仅通过网络数据包来进行通信。
通常,我会倾向于将系统的解耦推行到某种一旦有需要就可以随时转变为服务的程度即可,让整个程序尽量长时间地保持单体结构,以便给未来留下可选项。
一个设计良好的架构应该能允许一个系统从单体结构开始,以单一文件的形式部署,然后逐渐成长为一组相互独立的可部署单元,甚至是独立的服务或者微服务。最后还能随着情况的变化,允许系统逐渐回退到单体结构。
10. 小结
一个系统所适用的解耦模式可能会随着时间而变化,优秀的架构师应该能预见这一点,并且做出相应的对策。
第十七章:划分边界
软件架构设计本身就是一门划分边界的艺术。
- 边界的作用是将软件分割成各种元素,以便约束边界两侧之间的依赖关系。
- 其中有一些边界是在项目初期一甚至在编写代码之前——就已经划分好,而其他的边界则是后来才划分的。
- 在项目初期划分这些边界的目的是方便我们尽量将一些决策延后进行,并且确保未来这些决策不会对系统的核心业务逻辑产生干扰。
架构师们所追求的目标是最大限度地降低构建和维护一个系统所需的人力资源。一个系统最消耗人力资源的是系统中存在的耦合——尤其是那些过早做出的、不成熟的决策所导致的耦合。
1. 过早且不成熟决策
过早且不成熟决策——那些与系统的业务需求(也就是用例)无关的决策。