C4 模型:分层架构图绘制
什么是 C4 模型?
C4 模型是一种用于可视化软件架构的层级化方法。它将系统分解为四个抽象层次:上下文(Context)、容器(Container)、组件(Component) 和代码(Code)。每一张图都只回答一个特定的问题,避免一张图承载过多信息,让技术人员和非技术人员都能快速理解系统结构。
C4 模型的核心价值在于同一套抽象,不同角色看不同层级。你可以用统一的标准符号,从 30,000 英尺的鸟瞰图一路深入到类图细节,而不会丢失脉络。
第 1 层:系统上下文图
回答的问题
- 我们构建的系统在整体环境中扮演什么角色?
- 它服务哪些用户?它依赖哪些外部系统?
绘制要点
- 中心元素:用一个大方框表示你正在构建的软件系统,通常标注名称和主要职责。
- 人员角色:绘制人形图标,代表不同类型的用户(例如:顾客、客服、管理员)。
- 外部系统:用灰色方框表示第三方系统、遗留系统、外部服务等。
- 方向性关系:用带箭头的线展示数据或调用流向,并用标签简要说明交互意图(例如:“下订单”、“查询库存”)。
示例描述:一位“网上银行客户”通过“网上银行系统”查看余额,“网上银行系统”依赖“核心银行系统”获取账户数据。
这条图的受众
所有利益相关者,尤其是业务方、产品经理、架构评审委员会。它帮助大家在高层面达成“系统边界”的共识。
第 2 层:容器图
回答的问题
- 软件系统由哪些可独立部署的技术单元组成?
- 容器之间如何通信?技术选型是什么?
重要定义:这里的“容器”不是 Docker 容器,而是指一个可运行、存储数据或托管代码的独立单元,例如:Web 应用、移动 App、数据库、文件系统、消息队列、微服务、单页应用等。
绘制要点
- 在系统边界内,用不同颜色或图标区分容器类型(服务、数据库、客户端等)。
- 每个容器标注名称 + 技术选型(例如“Spring Boot 微服务”、“PostgreSQL 数据库”、“React 单页应用”)。
- 使用带箭头的线标注通信协议和交互目的,例如:“读取/写入(JDBC)”、“发送消息(AMQP)”、“REST API(HTTPS)”。
示例描述:“网上银行系统”内部包含一个“单页应用(React)”、“API 服务(Java/Spring Boot)”和“数据库(PostgreSQL)”。SPA 通过 JSON/HTTPS 调用 API,API 通过 JDBC 访问数据库。
这条图的受众
技术团队内部,尤其是开发人员、DevOps、架构师。它能清晰展示技术栈、部署拓扑和跨容器通信方式。
第 3 层:组件图
回答的问题
- 某个容器内部由哪些可独立工作的组件组成?
- 这些组件之间的职责和接口如何定义?
这里的“组件”是指在一个容器进程内部、通过某种接口组织起来的一组职责封装,通常是编程语言层面的模块、包、命名空间、分层结构或一组领域服务。
绘制要点
- 以选定的容器为边界,在其内部画出组件(用箱框表示)。
- 每个组件标注名称和核心职责(例如:“账户管理服务”、“交易引擎”、“通知模块”)。
- 组件间的关系通过接口名称或方法调用描述,例如:“IAccountRepository”、“INotificationSender”。
示例描述:在 API 容器中,Controller 层接收请求,调用“账户服务”组件,该组件进一步使用“账户仓储”接口访问数据库组件。同时还向“通知组件”发送异步事件。
这条图的受众
开发团队内部,常用于代码走查、模块设计评审、新成员 onboarding。它指导代码的分层与模块边界。
第 4 层:代码图
回答的问题
- 组件内部的具体实现细节如何组织?
- 类、接口、枚举等如何协作完成一个功能?
这一层通常可用 UML 类图、实体关系图或 IDE 自动生成的图表来表达。C4 模型并不强制要求画这一层,仅当关键复杂性需要被文档化时才绘制,避免过度维护。
绘制要点
- 使用标准的 UML 类图符号表达类、接口、字段、方法。
- 仅对重要的、易出错的、或架构上关键的组件进行代码级建模。
- 优先让图表由代码反向生成,保持与实现同步。
建议:不要为每个组件都绘制代码图,而是为复杂的业务规则或遗留代码中需重构的部分保留。
符号规范与绘制工具
统一元素
C4 模型有推荐的基本符号,但鼓励团队自定义并形成图例:
- 人员:人形图标
- 系统:蓝色方框
- 容器:不同颜色方框,附加技术图标
- 组件:浅色方框,位于容器内部
- 关系:实线(同步)、虚线(异步),带箭头与标签
推荐工具
- Structurizr:DSL(代码即图)方式创建 C4 模型,支持版本控制和多视图渲染。
- Draw.io / diagrams.net:提供 C4 形状库,适合快速手绘。
- PlantUML:通过 C4 扩展语法编写文本图。
- Mermaid:社区支持 C4 样式的渲染。
- Lucidchart / Excalidraw:协作白板工具。
绘制 C4 模型的最佳实践
1. 从外向内,逐层深入
先画系统上下文图,确保业务边界正确;再进入容器图定义技术单元;仅在需要细节时绘制组件图。避免跳过上下文图直接进入代码细节。
2. 保持图表的自解释性
每张图必须有标题、图例、明确的边界框和描述性标签。任何人无需额外文档就能读懂。
3. 用“单页约束”控制复杂度
每张图上的元素数量建议控制在 5~15 个之间。如果一个容器内的组件超过 15 个,考虑是否引入了过多细节,或应拆分为多张聚焦不同子域的组件图。
4. 关系要紧扣“为什么”
连线上的标签必须体现实质性的交互意图,而不是简单写“调用”。例如,“验证用户身份”优于“调用 A 服务”。
5. 版本化与自动化
将 C4 图写成 Structurizr DSL 或 PlantUML 代码,纳入版本控制。与 CI 集成,确保文档与代码一同演进。
6. 避免提前深入
90% 的架构讨论只需要上下文和容器图。只有当团队对齐模块职责、评审复杂逻辑或接手遗留系统时,才需要组件图。
上手练习:为一个“电商订单系统”绘制 C4 模型
步骤 1:系统上下文图
- 中心:电商订单系统
- 用户:买家、商家、客服
- 外部系统:支付网关、物流系统、邮件服务
- 关系:买家下订单 → 系统调用支付网关收款 → 成功后通知物流系统发货 → 邮件服务发送确认
步骤 2:容器图(电商订单系统内部)
- 买家端 Web App(React)
- 商家端管理后台(Vue.js)
- 订单核心服务(Go)
- MySQL 数据库
- Redis 缓存
- 消息队列(Kafka)
- 通信关系:前端通过 REST API 调用核心服务,核心服务读写数据库、缓存,并通过 Kafka 发布订单事件供物流和邮件服务消费。
步骤 3:组件图(订单核心服务容器内)
- 订单控制器、订单服务、库存校验组件、支付适配器、订单仓储接口
- 订单控制器接收请求 → 调用订单服务 → 组合库存校验和支付适配器 → 持久化通过订单仓储接口
完成这个练习后,你会对层级以及“何时画哪一层图”有直观感受。
常见误区
| 误区 | 正确做法 |
|---|---|
| 把所有层级的元素挤在一张图上 | 严格分层,每张图只关注一个抽象层次 |
| 在容器图中用 Docker 图标来表示容器 | 容器代表逻辑上的可部署单元,不是 Docker 容器 |
| 用箭头代表数据流,不加任何文字说明 | 必须添加标签明确交互目的和协议 |
| 每张图都深入代码级别 | 仅在必要的局部区域绘制代码图,避免维护噩梦 |
| 只有架构师才画 C4 | 整个团队可以共同维护同一套 .dsl 或 .puml 文件,形成活文档 |
C4 模型的核心是用一致的抽象语言,在不同细节级别上沟通架构意图。从今天开始,为你的系统尝试绘制第一张系统上下文图吧。