# 05. 系统拆分

如何按职责切分系统、把真正的 boundary 和偶然形成的文件夹区分开,以及如何在实践中运用 cohesion、coupling 和 bounded context。

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

---

## 1. 核心观点

好的边界,落在系统天然改变职责、改变语言、或改变变化速率的地方。

### 拆分从“变化的原因”开始

> 如果一扇门总因为仓库而被挪动,另一扇门总因为访客入口而被挪动,那就是楼里的两个区域。它们不该出现在同一面墙上。

一个 **high cohesion(高内聚)** 的组件,装的是那些"因同一个原因而变化"的东西。一个 **low coupling(低耦合)** 的组件,对邻居知之甚少,只通过清晰的 contract 对话。这两个属性,比任何 pattern 的名字都重要。

糟糕的拆分在 IDE 里往往看着挺整洁:`controllers`、`services`、`repositories`、`utils`。但如果一个小小的产品改动逼着你去碰五个层、三个领域,这个系统仍然切得很烂。好的拆分让你能改 checkout、billing 或 identity,而不必在脑子里把产品的其余部分全部加载进来。

### 边界同时也是 ownership

> 一张城市地图毫无用处,如果没人知道谁负责修桥、谁管红绿灯、出了事故该打给谁。

一条架构边界需要一个 owner。如果一个模块是"共享的",但人人都在改,它很快就会变成风险区。如果一个 bounded context 属于某个团队,那个团队就获得了演进内部模型的权利,以及保持外部 contract 稳定的义务。

所以拆分和 team topology 是绑在一起的。有时候,真正该问的不是"我们要抽出哪个 service",而是"哪个团队应该端到端地拥有这条 flow"。

## 2. 例子与反例

同一个领域,可以按表来切、按层来切,也可以按业务 flow 来切。通常第三种才管用。

### 例子:把 checkout 作为一个垂直切片

> 店里的收银台拥有整段购买旅程:购物车、价格、支付、收据。它不是"按钮的一层",也不是"SQL 的一层"。

在电商里,checkout 可以拥有 pricing snapshot、payment intent、order draft 和失败处理。Catalog 仍然拥有商品,identity 拥有用户,warehouse 拥有库存。checkout 不去复制别的团队的逻辑——它通过 contract 索要数据,并发布一个 `OrderPlaced` 事件。

这个垂直切片用起来很舒服:团队能完整看见用户 flow,而支付里的改动不会扩散到每一个子系统。

### 反例:把技术层当成唯一的边界

> 如果所有厨师都在一个房间切菜、在第二个房间煎、在第三个房间放盐,那么一份简单的订单就开始需要协调整栋楼。

系统被拆成了 `user-service`、`order-service`、`payment-service`,但每个 service 内部坐着一个共享的 `common-domain`、一个共享数据库、一套共享的 validator。形式上有 service,功能上没有边界。

当团队按层划分时会更糟:一个前端团队、一个后端团队、一个数据库团队。每个 feature 都变成一个协调项目。这会拉高 lead time 和 cognitive load。

#### 自检

- 这个模块"变化的原因"是什么?
- 团队能不能不跟五个邻居谈判就改动它?
- 这条边界有 owner 吗?
- 哪些数据和 invariant 真正属于这个 context?

下一模块:[Layers and dependencies](./software-architect-06-layers)。
