分层架构优劣:传统三层 vs 现代模式
分层架构优劣:传统三层 vs 现代模式
本教程将带你从经典的UI-BLL-DAL三层模型出发,逐步理解分层架构的设计初衷、不得不面对的代价,以及它在云原生与领域驱动时代如何演进为更健康的现代分层模式。无需预先掌握任何特定框架,只要对“系统为什么需要分层”有基本好奇即可。
1. 为什么要对系统进行分层?
把代码全部写在一个文件里,看起来“快速”,但不出三天就会变成无法维护的泥球。分层的目的可以被概括为三个核心愿望:
-
关注点分离(Separation of Concerns)
让处理用户界面的代码、处理业务规则的代码、处理数据库的代码各自独立,任何一方的变化不对其他层造成海啸式冲击。 -
可测试性
当业务逻辑与数据库实现解耦,我们就能在测试中用内存仓库替代真实数据库,快速、稳定地验证规则。 -
团队协作与演进
不同开发人员可以并行工作在视图、业务域、基础设施上,而且替换某一层的技术栈(如从 SQL Server 切换到 MongoDB)时无需重写整个系统。
然而,传统的三层模型在享受这些好处的同时,也埋下了若干结构性缺陷。下面我们先回到那个最熟悉的分层模型。
2. 传统三层架构:经典姿势与隐藏痛点
2.1 三层是什么?
经典三层架构把应用垂直切为:
-
表示层(Presentation Layer / UI)
负责接收用户输入、渲染界面。在 Web 应用中通常是 Controller 或 Razor 页面。 -
业务逻辑层(Business Logic Layer / BLL)
包含应用的核心规则、流程、校验。所有“做什么、能不能做”的判断都在这里。 -
数据访问层(Data Access Layer / DAL)
封装对数据库、文件系统、外部 API 的持久化操作,提供类似GetUserById这样的粗粒度方法。
数据流严格自上而下:UI → BLL → DAL,依赖方向也同向。
2.2 三层架构的优点
-
学习成本极低,上手迅速
几乎所有初级教程都以三层作为入门脚手架,概念直接,文件夹结构清晰。 -
快速产出 CRUD 应用
当业务单纯围绕“建表-展示-修改-保存”时,三层效率很高。 -
可进行初步的职责划分
初级开发者可以只编写 UI 逻辑,稍资深者处理数据访问,领域知识集中在 BLL。 -
物理部署灵活
各层可部署在同一进程内,也可以将 BLL 抽离为应用服务器,实现简单的横向扩展。
2.3 三层架构的致命弱点
尽管三层已然成为默认范式,但在实际复杂项目中它会逐渐显露以下问题:
-
依赖方向僵化,BLL 成为空心服务员
很多团队将 BLL 写成只管调用 DAL、再顺手传个null检查的中转站。真正的业务规则要么泄漏到 UI,要么沉入存储过程,最后 BLL 只剩下贫血模型(一堆只有 get/set 的 POCO 类加上几个 Service)。 -
跨层污染与横向关注点
日志、事务、认证、验证这些横切关注点往往在三层中四处散落,要么被硬编码进每个方法,要么靠静态工具类耦合整个代码库。 -
以数据库为中心
表结构驱动一切,领域概念没有独立生存空间。“增加一个字段”的变更会从数据库冒泡到 UI,每层都被迫修改。 -
测试难度意外升高
因为 BLL 直接依赖具体的 DAL 实现,单元测试必须 mock 掉整个仓储接口,且很难构造真正的领域行为测试。 -
变更响应迟钝
一旦需要将部分功能拆分为微服务,或尝试引入 NoSQL、消息队列,三层那种“一切穿过 DAL”的结构会产生巨大的摩擦。
这些问题并非否定分层思想本身,而是指出以“数据库操作顺序”来组织代码的不足。于是,更多的现代架构模式被引入,试图在不放弃分层好处的前提下解决这些痛点。
3. 现代分层模式:不再围绕数据库呼吸
现代分层思想并不是推翻“分层”,而是重新定义层的职责与依赖方向。
3.1 依赖倒置与领域核心
现代架构最关键的转变是:核心领域逻辑不再依赖基础设施,而是基础设施去实现由核心定义的接口。
这意味着:
- 不再有“业务层依赖数据层”,而是业务层定义
IUserRepository接口,数据层去实现它。 - 依赖方向从“外围→核心”,核心不依赖任何外部技术细节。
这一原则催生了数个著名架构风格。
3.2 六边形架构(端口与适配器)
六边形架构把应用看作一个由端口(Port)包围的六边形:
- 内部 → 领域模型与应用服务,纯业务,无框架侵入。
- 端口 → 用例定义的输入边界(如
CreateOrderUseCase)和输出边界(如OrderRepository接口)。 - 适配器 → 把真实技术设施(Web API、数据库、消息队列)适配成端口期望的样子。
好处:
- 切换 UI 框架或数据库不需要触碰领域代码。
- 测试时,每个适配器都可以被手工实现或 mock 轻松替换。
- 驱动开发的永远是用例,而非表结构。
3.3 洋葱架构(Onion Architecture)
洋葱架构进一步强调同心环结构,由内向外依次为:
- 领域层(Domain Layer) – 实体、值对象、领域服务、聚合,完全无外部依赖。
- 应用服务层(Application Layer) – 用例编排,调用领域对象与端口,不包含业务规则。
- 基础设施层(Infrastructure) – 实现仓储、文件系统、邮件发送等。
- 表现层(UI / API) – 与外部交互的适配器。
核心规则:所有依赖都指向圆心。内层定义抽象(接口/端口),外层实现。这将数据库推到了架构的最外缘,不再是核心。
3.4 整洁架构(Clean Architecture)
与洋葱架构思想一致,但更强调用例是架构的中心,且不依赖于任何框架。它通过依赖规则保证:
- 内层不了解外层
- 数据跨越边界时,永远由简单数据结构(DTO)传递,避免内层引用外层的类。
3.5 DDD 战术分层(领域驱动设计)
在领域驱动设计中,分层被明确为:
- 用户界面层 – 呈现信息、解释用户命令。
- 应用层 – 不包含业务规则,只做任务委派和事务协调。
- 领域层 – 包括聚合、实体、值对象、领域事件、规约、仓储接口。
- 基础设施层 – 为上层提供技术实现。
与三层最大的区别是:领域层拥有丰富的、自主执行业务规则的对象,而不是被动等待 Service 操作的贫血模型。
4. 传统三层 vs 现代模式:维度对比
| 维度 | 传统三层 | 现代分层模式(六边形/洋葱/整洁) |
|---|---|---|
| 核心关注 | 数据库与表结构 | 业务用例与领域概念 |
| 依赖方向 | UI → BLL → DAL | 外部适配器 → 用例/领域(核心) |
| 领域模型 | 通常贫血(只有数据传递) | 充血(包含行为与规则) |
| 基础设施耦合 | BLL 直接引用 DAL 实现 | 核心定义接口,基础设施实现它 |
| 测试隔离 | 需 mock 整个仓储层,测试复杂 | 可独立测试领域逻辑与适配器 |
| 变更影响 | 数据库变更向上穿透 | 领域接口稳定,外部变更局部化 |
| 学习曲线 | 低 | 中高(需理解依赖倒置、端口适配) |
| 适用场景 | 简单 CRUD、原型、短期项目 | 复杂业务、长寿命产品、微服务内核 |
| 代码组织方式 | 按层(按技术职责)分包 | 按领域/特性/用例分包 |
5. 实际落地建议:你不需要一次性“搞架构”
5.1 什么时候三层就够了?
如果系统满足以下大多数条件,传统三层依然是好选择:
- 数据模型与业务逻辑高度一致(如后台管理系统)。
- 团队经验集中在三层,迭代压力紧。
- 技术栈切换可能性极低。
- 并发、事件驱动、微服务等诉求当前不需要。
实用技巧:即便保留三层,也可以将三方库(EF Core、HttpClient)封装进接口,避免直接依赖,为未来架构演进埋下种子。
5.2 何时拥抱现代分层?
- 业务复杂度已经超过 CRUD,例如订单取消涉及库存恢复、退款、积分回退等多聚合协作。
- 需要支持多渠道(Web、移动端、API 公开给第三方),并且希望复用以用例为核心的逻辑。
- 检验性和维护性是第一位,项目会被长期传承,人员流动较大。
- 逐步向微服务拆分,需要先将核心领域从技术框架中解放出来。
5.3 渐进式演进策略
不要试图一次把整个系统从三层“重写”为整洁架构。推荐路径:
- 分离领域模型:把纯 POCO 类移到一个 Domain 项目,初期依然允许贫血,但逐渐将简单校验方法移入实体。
- 抽取仓储接口:由 Domain 定义
IRepository,在基础设施实现,将 BLL 的依赖反转。 - 引入应用服务层:将原先 BLL 中的流程类代码迁移至应用服务,而把规则放入领域对象,让 BLL 成为历史名词。
- 用适配器替换直接引用:每次接入新服务(短信、支付)时,先用端口适配器模式实现,从而锻炼团队。
- 按用例拆解:将某些早期三层模块按用例重新组织为垂直切片,验证新模式。
6. 常见误区与自检清单
-
误区:分层就是建大量项目/文件夹
分层是逻辑依赖约束,而不是物理项目数量。切勿为了“层次分明”而建立十几个仅有一两个类的项目。 -
误区:只要用了 IoC 容器就是做了依赖倒置
依赖倒置的核心是“高层模块不依赖低层模块,两者都依赖抽象”,而不仅是把依赖注入换成接口。 -
自检问题(用以下问题审查你自己的分层):
- 要换掉数据库,需要改动多少个项目?
- 能否不依赖任何具体框架就运行领域层的单元测试?
- 是否每个用例都能用一句话完整表述,且不涉及技术术语?
- 横切关注点(日志、事务)是否大量重复出现?
7. 总结:分层的真正价值不在层,而在边界
分层本身不是目的,隔离变化、保护投资才是。传统三层在软件发展史上扮演了重要角色,它将“前端、逻辑、数据”的分离观念变成了开发本能;现代分层模式则将这一本能优化为业务逻辑独立、技术依赖可替换的生存法则。
因此,评价一个分层架构的优势,不该看它用了多少层或叫法多么新颖,而在于:
- 业务意图是否清晰易读;
- 修改成本是否聚焦在变动的原因附近;
- 核心逻辑能否摆脱对易变技术的寄生。
无论你选择传统三层起步,还是直接采用洋葱/整洁架构,请务必记住:好的架构是让代码讲故事,而不是让技术细节唱主角。
延伸学习:建议接着阅读《领域驱动设计》的前四章,以及探索“垂直切片架构”与“模块化单体”等概念,它们会进一步拓宽你对“分层”一词的理解。