架构-C4模型:把架构图画到合适的层级
很多架构图的问题,不是画得不够多,而是把不同层级的东西放在了一起。
用户、外部系统、服务、数据库、业务模块和具体类同时出现在一张图里。读者看完仍然不知道系统边界在哪里,也不知道箭头表示调用、依赖还是数据流。这样的图通常只能由作者自己解释,无法作为团队共同使用的文档。
C4 模型针对的就是这个问题。它把架构拆成四个观察层级,让读者从系统边界一路看到代码结构:
text
Context → Container → Component → Code
系统边界 运行单元 业务模块 代码结构
C4 模型由 Simon Brown 提出。它不替你选择单体、微服务或某种数据库,只规定如何把这些设计表达清楚。
四个层级怎么区分
| 层级 | 要回答的问题 | 可以放什么 |
|---|---|---|
| Context | 系统服务谁,依赖谁? | 用户、当前系统、外部系统 |
| Container | 系统由哪些运行单元组成? | 应用、服务、数据库、消息队列 |
| Component | 某个运行单元内部如何分工? | 控制器、业务服务、适配器 |
| Code | 关键模块由什么代码实现? | 类、接口、继承和依赖 |
这里的 Container 不是 Docker 的同义词。一个桌面客户端、一个后端服务或一个数据库,都可以作为 C4 的 Container。
也不必四层全画。大多数项目画清 Context 和 Container 就够了;只有在某个服务复杂到需要专门讨论时,才继续画 Component。Code 图更适合解释一个关键设计,而不是展示整个项目的类目录。
用订单系统举例
Context:先说清楚系统边界
假设要介绍一个订单系统。Context 图只放客户、订单系统以及它依赖的外部系统:

这张图解决两个问题:谁在使用订单系统,订单系统和谁交互。支付平台和仓储系统在边界之外;数据库、接口类和业务模块不应该出现在这里。
Container:再说明系统由什么组成
展开订单系统后,可以看到 Web 前端、订单 API、订单数据库、消息队列和库存服务:

这里需要让读者看懂运行关系。例如,Web 前端通过 HTTP 调用订单 API,订单 API 写入订单数据库并发布消息,库存服务消费消息。只写"API → 队列"是不够的,至少要说明通信方向和用途。
Component 和 Code:复杂处再展开
如果订单 API 已经承担了太多职责,可以把它拆成几个组件:
text
订单 API
├── 订单控制器:接收并校验请求
├── 创建订单服务:编排下单流程
├── 库存网关:访问库存服务
├── 支付协调器:处理支付调用
└── 订单仓储:读写订单数据
这时讨论的是模块职责,不是文件夹结构。只有在需要解释接口设计、依赖方向或关键类时,才继续画 Code 图。一个简单服务如果已经能在 Container 图中讲清楚,就应该停在这里。
怎样从零开始绘制
先决定这张图给谁看
读者决定细节:
- 给产品、管理者或新成员介绍系统,画 Context;
- 讨论系统拆分和技术依赖,画 Container;
- 评审某个复杂服务,画 Component;
- 讨论关键实现,画 Code。
不要试图用一张图满足所有人。一张图只解决一个主要问题,读者才知道应该关注什么。
先写出系统边界
用一句话回答:
这个系统负责什么,不负责什么?
把当前系统放在边界内,把用户、外部系统和基础设施放在边界外。边界没定下来就开始画服务和数据库,最后得到的往往只是依赖清单。
绘制 Context
在中心放当前系统,周围放直接使用它的用户和它主动依赖的外部对象。关系要写业务含义,例如"提交订单""发起支付""同步库存"。这一层不画数据库、类名和内部模块。
拆分 Container
从当前系统内部找出可以独立运行、部署,或承担明确职责的单元:前端、桌面客户端、后端服务、数据库、消息队列、设备适配服务等。
每个 Container 至少写清名称、职责和技术形态:
text
名称:订单 API
职责:接收订单请求,协调支付、库存和持久化
技术:HTTP 服务
箭头也要写清楚。相比"订单 API → 支付平台","订单 API 通过 HTTPS 发起支付请求 → 支付平台"能减少很多误解。
按职责划分 Component
Component 不应只是代码目录的另一种画法。按职责拆分,确保每个组件都能用一句话说明自己的工作,并通过明确的接口与其他组件协作。
Code 只画关键部分
Code 图可以展示核心接口、重要依赖和需要评审的类。不要把整个项目的类全部导出;那会变成一张难以维护的代码地图,也无法突出设计重点。
画完后检查
- 一张图是否只表达一个抽象层级?
- 每个元素是否写了名称和职责?
- 每条箭头是否有方向、用途或通信方式?
- 是否混入了与当前问题无关的细节?
- 图中的结构是否仍符合当前代码和部署状态?
如果图已经塞不下重点,不要继续缩小字体。把复杂部分拆到下一层,或者改用时序图、部署图或数据模型图。
C4 不负责什么
C4 用来说明"系统由什么组成"。下面这些问题应交给其他图:
- 时序图:对象如何按时间交互;
- 部署图:系统运行在哪些节点;
- ER 图:数据实体如何关联;
- 流程图:业务步骤如何推进。
把这些内容硬塞进 C4 图,只会让图重新变得难读。
结语
C4 的用处,是让信息出现在合适的层级。先把系统边界和外部关系说清楚,再说明运行单元;只有确实需要时,才深入模块和代码。架构图画到读者已经能做出判断的地方,就该停笔。