架构的终极目标:价值与选择
软件架构的终极目标非常明确:用最小的人力成本来满足构建和维护该系统的需求。这个目标包含两个价值维度:
- 行为价值:让系统满足当前的功能需求,这是系统的基础使命。
- 结构价值:让系统保持软件的“柔性”,使其在未来能够轻松被理解、修改、维护和部署。一个好的架构,其价值在于为未来保留尽可能多的选择权。
三大编程范式:架构的基石
软件架构并非空中楼阁,它建立在编程范式之上。这三大范式从不同维度限制了我们的编码方式,共同构成了现代软件设计的基础。
- 结构化编程:它通过限制
goto
等直接控制权转移,确立了顺序、分支、循环为程序构建的基础,为我们编写可证伪、可理解的功能模块提供了理论依据。 - 面向对象编程:其核心是多态。通过多态实现依赖反转,限制了函数指针的滥用,使得高层策略与底层实现细节解耦,从而构建出灵活的“插件式”架构。
- 函数式编程:它通过限制“赋值”操作,强调数据的不可变性。这为我们提供了强大的数据管理武器,能有效隔离副作用,让系统状态更可控。
这三者共同支撑起一个整洁的架构:结构化编程实现功能,面向对象划分组件边界,函数式编程管理数据状态。
SOLID原则:高内聚、低耦合的灵魂
SOLID 原则是模块设计的核心指导,它们是实现软件“柔性”的具体战术。
- SRP(单一职责原则): 任何一个模块,都应该只对某一类行为者负责,有且仅有一个被修改的原因。这是划分组件和架构边界的根本。
- OCP(开闭原则): 对扩展开放,对修改关闭。优秀的架构师会通过隔离变化,使得高阶组件不因低阶组件的修改而受影响。
- LSP(里氏替换原则): 子类型必须能够替换掉它们的基类型。这保证了抽象的可靠性,是多态得以实现的基础。
- ISP(接口隔离原则): 不应强迫用户依赖他们不需要的接口。这能避免不必要的依赖引入,保持组件的纯净。
- DIP(依赖反转原则): 源代码的依赖方向,永远与控制流的方向相反。高层策略不应依赖于底层细节,两者都应依赖于抽象。这是解耦最核心的原则。
组件构建:从代码到可部署单元
当我们将模块组合成可独立部署的组件(如 JAR、DLL)时,需要遵循更宏观的原则。
组件聚合三原则
- REP(复用/发布等同原则): 软件复用的最小粒度应等同于其发布的最小粒度。
- CCP(共同闭包原则): 将那些会因为相同原因、在同一时间被修改的类放到一个组件中。这与 SRP 和 OCP 息息相关,强调可维护性。
- CRP(共同复用原则): 不要强迫一个组件的用户依赖他们不需要的东西。这与 ISP 相关,强调解耦。
在实践中,项目早期我们可能更关注 CCP 以提高开发效率;随着项目发展,我们会转向 REP 以便复用;项目成熟后,CRP 则帮助我们拆分出更独立的组件。
组件耦合三原则
这三个原则指导我们如何设计组件间的依赖关系。
- 无循环依赖原则: 组件依赖关系图中不能出现环。可以通过DIP或引入新的公共组件来打破循环。
- 稳定依赖原则(SDP): 依赖关系必须指向更稳定的方向。一个易变的组件不应该被一个稳定的组件所依赖。
- 稳定抽象原则(SAP): 一个组件的抽象化程度应该与其稳定性保持一致。稳定的组件应该是抽象的,不稳定的组件应该是具体的。
整洁架构的核心:边界与依赖
这个同心圆模型清晰地展示了软件系统的分层结构:
- 业务实体 (Entities): 代表企业核心业务逻辑,最独立、最稳定、最可复用。
- 用例 (Use Cases): 封装和实现特定应用场景的业务规则,协调实体完成任务。
- 接口适配器(Interface Adapters): 将用例层和实体层的数据,转换为适合外部机构(如UI、数据库)的格式,反之亦然。
- 框架和驱动(Frameworks & Drivers): 最外层,由数据库、Web框架、UI等所有外部细节组成。
整洁架构最关键的规则是:依赖关系只能由外向内。
这意味着,业务核心逻辑(实体、用例)完全不知道、也不关心外部的数据库、UI、网络是如何实现的。外部细节是“插件”,可以随时被替换,而不会影响到核心业务。
实践中的智慧
- 尖叫的架构:一个好的架构,看到它的顶层目录结构就应该能“尖叫”出它的业务用途(例如这是一个电商系统),而不是“尖叫”出它用了什么框架(例如这是一个Spring系统)。
- 边界的划分:边界是系统变更的“断层线”。我们应该沿着变更轴划分边界,利用依赖反转将核心业务与IO等细节隔离开。
- 谦卑对象模式:这是一种强大的测试技巧。将系统中难以测试的部分(如UI渲染、数据库读写)抽离成极其简单的“谦卑对象”,而将所有复杂的逻辑都放在易于测试的模块(如Presenter)中。
- 数据库和框架都是实现细节:永远不要让你的核心业务逻辑与数据库或特定框架深度绑定。它们只是工具,是可替换的细节。
- Main组件是最终的“插件”:
main
函数或类似的启动模块是整个系统的最外层,它负责创建所有对象、注入依赖,将整个系统“粘合”起来。它是一个“脏”组件,但它的存在将所有其他组件的“干净”衬托了出来。 - 服务化不是架构:微服务是一种部署策略,而非架构本身。一个由深度耦合的服务组成的系统,其维护成本可能比一个设计良好的单体应用更高。真正的架构在于服务内部是否遵循了整洁的原则。