架构之Clean Architecture
Clean Architecture 是 Robert C. Martin 提出的一套架构原则。它把软件划分成同心圆,强调业务规则位于中心,具体技术位于外围,所有依赖都指向中心。
它的核心不是某一种目录结构,而是依赖规则:内层代码不能依赖外层代码,越靠近中心越稳定,越靠近中心越不应该变化。
一、Clean Architecture 的同心圆
#mermaid-svg-1e1Qjt6OtDKXtSXb{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1e1Qjt6OtDKXtSXb .error-icon{fill:#552222;}#mermaid-svg-1e1Qjt6OtDKXtSXb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1e1Qjt6OtDKXtSXb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .marker.cross{stroke:#333333;}#mermaid-svg-1e1Qjt6OtDKXtSXb svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1e1Qjt6OtDKXtSXb p{margin:0;}#mermaid-svg-1e1Qjt6OtDKXtSXb .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster-label text{fill:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster-label span{color:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster-label span p{background-color:transparent;}#mermaid-svg-1e1Qjt6OtDKXtSXb .label text,#mermaid-svg-1e1Qjt6OtDKXtSXb span{fill:#333;color:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .node rect,#mermaid-svg-1e1Qjt6OtDKXtSXb .node circle,#mermaid-svg-1e1Qjt6OtDKXtSXb .node ellipse,#mermaid-svg-1e1Qjt6OtDKXtSXb .node polygon,#mermaid-svg-1e1Qjt6OtDKXtSXb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .rough-node .label text,#mermaid-svg-1e1Qjt6OtDKXtSXb .node .label text,#mermaid-svg-1e1Qjt6OtDKXtSXb .image-shape .label,#mermaid-svg-1e1Qjt6OtDKXtSXb .icon-shape .label{text-anchor:middle;}#mermaid-svg-1e1Qjt6OtDKXtSXb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .rough-node .label,#mermaid-svg-1e1Qjt6OtDKXtSXb .node .label,#mermaid-svg-1e1Qjt6OtDKXtSXb .image-shape .label,#mermaid-svg-1e1Qjt6OtDKXtSXb .icon-shape .label{text-align:center;}#mermaid-svg-1e1Qjt6OtDKXtSXb .node.clickable{cursor:pointer;}#mermaid-svg-1e1Qjt6OtDKXtSXb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .arrowheadPath{fill:#333333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1e1Qjt6OtDKXtSXb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1e1Qjt6OtDKXtSXb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1e1Qjt6OtDKXtSXb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster text{fill:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb .cluster span{color:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1e1Qjt6OtDKXtSXb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1e1Qjt6OtDKXtSXb rect.text{fill:none;stroke-width:0;}#mermaid-svg-1e1Qjt6OtDKXtSXb .icon-shape,#mermaid-svg-1e1Qjt6OtDKXtSXb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1e1Qjt6OtDKXtSXb .icon-shape p,#mermaid-svg-1e1Qjt6OtDKXtSXb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1e1Qjt6OtDKXtSXb .icon-shape .label rect,#mermaid-svg-1e1Qjt6OtDKXtSXb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1e1Qjt6OtDKXtSXb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1e1Qjt6OtDKXtSXb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1e1Qjt6OtDKXtSXb :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
实体 / 核心业务规则
用例 / 应用业务规则
接口适配器
框架与驱动器
常见划分如下:
| 实体层 | 企业级或领域级核心业务规则 |
| 用例层 | 具体应用流程和业务规则 |
| 接口适配层 | Controller、Presenter、Gateway 等转换 |
| 框架与驱动层 | Web、数据库、UI、外部服务 |
二、依赖规则
Clean Architecture 最重要的一条规则是:
源代码依赖只能从外向内,不能从内向外。
#mermaid-svg-TII3yQWpwG7U8Rjb{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TII3yQWpwG7U8Rjb .error-icon{fill:#552222;}#mermaid-svg-TII3yQWpwG7U8Rjb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TII3yQWpwG7U8Rjb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TII3yQWpwG7U8Rjb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TII3yQWpwG7U8Rjb .marker.cross{stroke:#333333;}#mermaid-svg-TII3yQWpwG7U8Rjb svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TII3yQWpwG7U8Rjb p{margin:0;}#mermaid-svg-TII3yQWpwG7U8Rjb .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster-label text{fill:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster-label span{color:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster-label span p{background-color:transparent;}#mermaid-svg-TII3yQWpwG7U8Rjb .label text,#mermaid-svg-TII3yQWpwG7U8Rjb span{fill:#333;color:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb .node rect,#mermaid-svg-TII3yQWpwG7U8Rjb .node circle,#mermaid-svg-TII3yQWpwG7U8Rjb .node ellipse,#mermaid-svg-TII3yQWpwG7U8Rjb .node polygon,#mermaid-svg-TII3yQWpwG7U8Rjb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TII3yQWpwG7U8Rjb .rough-node .label text,#mermaid-svg-TII3yQWpwG7U8Rjb .node .label text,#mermaid-svg-TII3yQWpwG7U8Rjb .image-shape .label,#mermaid-svg-TII3yQWpwG7U8Rjb .icon-shape .label{text-anchor:middle;}#mermaid-svg-TII3yQWpwG7U8Rjb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TII3yQWpwG7U8Rjb .rough-node .label,#mermaid-svg-TII3yQWpwG7U8Rjb .node .label,#mermaid-svg-TII3yQWpwG7U8Rjb .image-shape .label,#mermaid-svg-TII3yQWpwG7U8Rjb .icon-shape .label{text-align:center;}#mermaid-svg-TII3yQWpwG7U8Rjb .node.clickable{cursor:pointer;}#mermaid-svg-TII3yQWpwG7U8Rjb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TII3yQWpwG7U8Rjb .arrowheadPath{fill:#333333;}#mermaid-svg-TII3yQWpwG7U8Rjb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TII3yQWpwG7U8Rjb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TII3yQWpwG7U8Rjb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TII3yQWpwG7U8Rjb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TII3yQWpwG7U8Rjb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TII3yQWpwG7U8Rjb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster text{fill:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb .cluster span{color:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TII3yQWpwG7U8Rjb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TII3yQWpwG7U8Rjb rect.text{fill:none;stroke-width:0;}#mermaid-svg-TII3yQWpwG7U8Rjb .icon-shape,#mermaid-svg-TII3yQWpwG7U8Rjb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TII3yQWpwG7U8Rjb .icon-shape p,#mermaid-svg-TII3yQWpwG7U8Rjb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TII3yQWpwG7U8Rjb .icon-shape .label rect,#mermaid-svg-TII3yQWpwG7U8Rjb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TII3yQWpwG7U8Rjb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TII3yQWpwG7U8Rjb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TII3yQWpwG7U8Rjb :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
外层的 Framework
Interface Adapters
Use Cases
Entities
这意味着:
- 实体不能知道用例层如何工作;
- 用例不能知道使用哪个数据库或 Web 框架;
- 接口适配层只负责协议和模型转换;
- 框架与驱动层可以被替换,而不影响核心规则。
三、依赖倒置如何实现
如果用例层需要使用数据库,它不能直接依赖数据库实现,而要在用例层定义接口:
public interface OrderRepository {
Optional<Order> findById(OrderId id);
void save(Order order);
}
数据库实现放在外层:
public class JpaOrderRepository implements OrderRepository {
// 依赖 JPA 的实现
}
依赖关系是:
JpaOrderRepository -> OrderRepository
OrderRepository <- UseCase
这样,数据库实现依赖用例层定义的接口,而不是用例层依赖数据库。这是 Clean Architecture 能保持核心稳定的关键。
四、Clean Architecture 与分层架构的区别
传统分层架构通常按技术分层,Clean Architecture 按业务稳定程度分层。
| 核心依据 | 技术角色 | 业务规则与稳定程度 |
| 领域模型位置 | 可能在中间或下层 | 永远在最中心 |
| 依赖方向 | 上层依赖下层 | 外层依赖内层 |
| 可测试性 | 依赖容器和数据库较多 | 核心逻辑可纯内存测试 |
| 框架地位 | 常常贯穿全系统 | 只存在于最外层 |
五、Clean Architecture 的优点
- 业务规则独立于框架,容易测试;
- 数据库、Web、UI 可以独立替换;
- 长期演进中,核心业务不容易被技术细节污染;
- 模块边界清晰,团队更容易理解依赖关系;
- 与洋葱架构、六边形架构可以互相借鉴。
六、常见误用
| 目录套壳 | 只建了 entity、usecase、adapter 包,没有真正约束依赖 |
| 贫血实体 | 业务规则全部跑到了用例层之外 |
| 过度转换 | 每一层都做大量重复 DTO 转换 |
| 过度抽象 | 简单 CRUD 也引入复杂接口 |
| 循环依赖 | 内层通过隐式方式依赖外层 |
Clean Architecture 并不要求所有项目都实现完全一样的目录。它的重点是依赖方向、测试能力和稳定边界的清晰程度。
结语
Clean Architecture 的目标,是让软件中最有价值的东西远离最易变化的东西。业务规则应该比数据库长寿,领域概念应该比框架更稳定。
当团队把依赖方向变成代码层面的硬约束,架构就不再只是画图,而成为每天写代码时都能执行的纪律。
清洁的不是目录,而是依赖。
让业务规则留在中心,让技术细节留在边缘。
框架会更换,核心不该随它一起坍塌。

