# 06. 分层与依赖

如何让依赖指向业务逻辑、ports and adapters 在哪里才真正物有所值,以及为什么 framework 不该变成架构本身。

- **Author:** Fedor Chishchin
- **Level:** advanced
- **Reading time:** 约 25 分钟阅读
- **Published:** 2026-05-23
- **Source:** https://fedorthinks.com/zh/learn/software-architect-06-layers

---

## 1. 核心观点

业务规则应该依赖领域的语言,而不是依赖"一个请求是怎么送进来的"。

### 细节属于外圈

> 房子的地基不能取决于窗帘的颜色。窗帘你随时能换,地基则几乎换不动。

Clean Architecture、Hexagonal Architecture 和 Onion Architecture 讲的是同一个观点:系统的中心装领域规则和 use case,外层装细节。REST controller、SQL 查询、Redis cache、Stripe SDK、message broker——都是重要的细节,但都不是业务规则的来源。

**Dependency Inversion Principle(依赖倒置原则)** 让箭头始终指对方向:高层 policy 依赖抽象,实现从外部插进来。落到实践里,它长这样:一个 `PaymentGateway` port,加上一个 `StripePaymentGateway` adapter。

### 一个层必须真的在干活

> 两个房间之间的空走廊,如果有人穿行,它就有用。如果它只是拉长了距离,那就是浪费的楼面。

当一个层带来隔离、可测试性,或一个独立的变化速率时,它就站得住脚。application 层协调一个 use case。domain 层装规则和 invariant。infrastructure 层与外部世界对话。

一个只是把参数从一个方法搬进另一个方法的层,不是架构,是仪式。架构师必须同时防住两头:缺失的边界,以及毫无意义的分层。

## 2. 例子与反例

可测试性能很快告诉你:依赖到底指对了方向没有。

### 例子:在 unit test 里不碰数据库就创建一个订单

> 要验证一份食谱,你不必去开一家餐厅。一间厨房和食材就够了。

`CreateOrder` 这个 use case 把 `InventoryPort`、`PaymentPort` 和 `OrderRepository` 作为依赖收进来。在 unit test 里,它们被换成 fake。测试只验证一条规则:没有固定的价格和一个成功的 payment intent,订单就无法被创建。

数据库和 HTTP 不属于这个测试。这意味着业务规则已经和 infrastructure 分开了。

### 反例:把 Active Record 当成宇宙的中心

> 如果所有逻辑都住在工具箱里,那么每次维修都得先翻找那把对的螺丝刀。

ORM model 验证业务规则、发起网络调用、发邮件、写 audit log,还决定权限。在系统还小的时候,这感觉很方便。后来,每个测试都要启动一个数据库,每次改动都拖着一堆 side effect,而 domain 变得无法复用。

问题不在 ORM 本身。问题在于:infrastructure model 最后拥有了业务 policy。

#### 自检

- 核心规则能不能在没有数据库、没有 HTTP 的情况下被验证?
- 哪些依赖向内流,哪些向外流?
- framework 在哪里 替你规定了 domain model?
- 哪些 adapter 能在不碰 use case 的前提下被替换?

下一模块:[APIs and contracts](./software-architect-07-api-contracts)。
