第 2 章:分层架构的起源:从单体应用到分层
大家好,欢迎来到第二章。
这一章我们聊聊软件架构的演进。说白了,就是看看我们是怎么从“一锅乱炖”走到“分而治之”的。
我刚开始写代码那会儿,项目结构很简单。一个 .c 文件,或者一个 .java 文件,从头写到尾。数据库连接、业务逻辑、界面渲染,全揉在一起。那时候项目小,一个人能搞定。但后来项目大了,问题就来了。
嗯,这就是我们今天要聊的起点——单体架构。
2.1 单体架构:最初的“大泥球”
单体架构(Monolithic Architecture)是最早期的软件组织方式。所有功能模块、数据处理、用户界面,都打包在同一个可执行文件或部署单元里。
你想想看,一个典型的单体应用长什么样?
- 一个 WAR 包或 EXE 文件,包含了所有代码
- 一个数据库实例,所有表都在里面
- 一个进程,处理所有请求
- 团队所有人,都在同一个代码仓库里改代码
听起来简单粗暴,对吧?
我在 2010 年左右接手过一个电商后台系统,就是典型的单体架构。一个 Tomcat 实例,部署了一个 200 多 MB 的 WAR 包。里面订单、商品、用户、支付,全在一个工程里。刚开始还好,但后来每次发布都提心吊胆。改一行代码,整个系统都要重新编译、打包、部署。更可怕的是,一个模块出问题,整个系统都可能挂掉。
- 耦合度高:模块之间直接调用,牵一发而动全身
- 扩展性差:只能整体水平扩展,无法针对热点模块单独扩容
- 维护成本高:代码量大了以后,新人上手极慢
- 部署风险大:一次发布影响所有功能
为什么会这样?因为所有代码都在同一个地址空间里运行。没有边界,没有隔离。就像一个大厨房,所有厨师都在同一个灶台上炒菜,互相干扰。
2.2 分层的萌芽:从“分离关注点”开始
为了解决单体架构的问题,前辈们开始思考:能不能把不同职责的代码分开?
这就是“分离关注点”(Separation of Concerns)的思想。说白了,就是让每一层只关心自己的事。
我记得最早接触分层概念,是在读 Martin Fowler 的《企业应用架构模式》那本书。他提出了三层架构的经典模式:
- 表现层(Presentation Layer):处理用户界面和交互
- 业务逻辑层(Business Logic Layer):处理核心业务规则
- 数据访问层(Data Access Layer):处理数据库操作
这个模式后来成了企业级应用的事实标准。直到今天,大部分 Web 应用还在沿用这个思路。
你可能会问:为什么是三层?不是两层或四层?
其实,层数不是固定的。关键在于:每一层只依赖它的下一层,不能跨层调用。这就是分层架构的核心约束。
2.3 分层架构的核心原则
分层架构不是简单地把代码放到不同文件夹里。它有严格的规则。
| 原则 | 说明 | 违反后果 |
|---|---|---|
| 单向依赖 | 上层只能依赖下层,不能反向依赖 | 循环依赖,编译失败 |
| 封装性 | 每层只暴露必要的接口,隐藏实现细节 | 层间耦合,修改困难 |
| 替换性 | 下层可以被替换,只要接口不变 | 技术栈锁定,无法升级 |
| 关注点分离 | 每层只处理自己的职责 | 代码混乱,职责不清 |
这些原则听起来简单,但实际落地时,坑很多。
我曾经在一个项目中,业务逻辑层直接返回了 DataSet 对象给表现层。表现层拿到 DataSet 后,直接遍历行和列来渲染表格。这看起来“方便”,但实际上破坏了封装性。后来数据库表结构一改,表现层代码全要跟着改。这就是典型的“层间泄漏”。
2.4 分层架构的典型结构
我们来看一个标准的四层架构(在经典三层基础上增加了基础设施层):
┌─────────────────────────────────┐
│ 表现层 (UI Layer) │
│ - 控制器 (Controller) │
│ - 视图 (View) │
│ - 视图模型 (ViewModel) │
├─────────────────────────────────┤
│ 应用层 (Application Layer)│
│ - 服务 (Service) │
│ - 数据传输对象 (DTO) │
│ - 应用事件 (Application Event) │
├─────────────────────────────────┤
│ 领域层 (Domain Layer) │
│ - 实体 (Entity) │
│ - 值对象 (Value Object) │
│ - 领域服务 (Domain Service) │
│ - 仓储接口 (Repository Interface)│
├─────────────────────────────────┤
│ 基础设施层 (Infrastructure) │
│ - 数据访问 (Data Access) │
│ - 消息队列 (Message Queue) │
│ - 外部服务 (External Service) │
│ - 缓存 (Cache) │
└─────────────────────────────────┘
注意看,依赖方向是从上到下的。表现层依赖应用层,应用层依赖领域层,领域层依赖基础设施层。反过来不行。
这里有个关键点:领域层不依赖任何具体的技术实现。它只定义接口(比如 Repository 接口),具体的数据库操作由基础设施层实现。这就是“依赖倒置原则”(Dependency Inversion Principle)在分层架构中的应用。
2.5 分层架构的优缺点
任何架构模式都不是银弹。分层架构也有它的适用场景和局限性。
| 优点 | 缺点 |
|---|---|
| 结构清晰,职责明确 | 层数增加,性能有损耗 |
| 易于维护和扩展 | 小项目可能过度设计 |
| 支持并行开发 | 层间通信需要定义接口 |
| 技术栈可替换 | 学习曲线较陡 |
| 便于测试(可 mock 下层) | 可能出现“胖服务层” |
我个人习惯是:项目初期不要过度分层。先按三层架构搭起来,等业务复杂度上来了,再考虑是否要拆出独立的领域层或基础设施层。否则,你可能会陷入“为分层而分层”的陷阱。
2.6 分层架构的演进:从物理分层到逻辑分层
早期的分层是物理上的。表现层在一台服务器上,业务逻辑层在另一台,数据层在第三台。这叫“物理分层”。
但后来大家发现,物理分层带来了网络开销和部署复杂度。于是,逻辑分层成了主流。所有层都在同一个进程中,但通过接口和命名空间来隔离。
再后来,微服务架构兴起。它把分层的思想进一步推向了极端——每个服务都是一个独立的进程,有自己的分层结构。服务之间通过 API 通信。
但不管怎么变,分层的核心思想始终没变:通过定义清晰的边界和接口,降低系统的复杂度。
2.7 本章小结
我们从单体架构的痛点出发,看到了分层架构是如何一步步解决这些问题的。核心就三点:
- 分离关注点:每层只做一件事
- 单向依赖:上层依赖下层,不反向
- 接口定义:层与层之间通过接口通信,而不是直接调用实现
下一章,我们会深入探讨“接口定义”这个核心话题。毕竟,分层架构能不能落地,关键就看接口怎么设计。
嗯,今天就到这里。希望你能从这一章里,看到分层架构背后的设计哲学。它不只是技术,更是一种思维方式。