Table of contents
Open Table of contents
TL;DR
C4 Model 借鉴地图学的缩放思想,用 System Context → Container → Component → Code 四层递进抽象,让不同受众在对应层次获取所需信息,解决传统架构图”一张图什么都画、什么都看不清”的问题。
传统架构图为什么失败
大多数架构图是白板上的”随意方框 + 箭头”,核心问题:
- 抽象层次混乱 — 同一张图里,一个方框可能是微服务,另一个是 Java 类,还有一个是数据库表,没人知道每个方框代表什么粒度
- 没有标准词汇 — “组件”这个词对不同人意味着 Spring Bean / Docker 容器 / npm 包 / DLL,沟通无效
- 受众错配 — 一张图试图同时服务 CEO、开发、运维,结果三方都看不懂
- 图即死亡 — 画完就过时,永远不更新,最终成为误导源
- UML 的矫枉过正 — 14 种图类型 + 数百种标记符号,学习曲线太陡,大多数团队直接放弃
C4 的核心洞察:缩放
C4 = Context, Container, Component, Code。
灵感来自地图学:世界地图、国家地图、城市地图、街道地图展示同一片领土,但缩放层级不同,服务不同受众。
| 层级 | 展示什么 | 受众 | 关键问题 | 推荐程度 |
|---|---|---|---|---|
| L1 System Context | 系统在生态中的位置:用户是谁、依赖哪些外部系统 | 所有人(含非技术) | 这个系统是什么?谁用它? | 必画 |
| L2 Container | 系统内部的可部署单元:Web App / API / DB / MQ 及通信协议 | 技术团队 | 系统由哪些部署单元组成?它们怎么通信? | 必画 |
| L3 Component | 单个 Container 内部的模块划分:Controller / Service / Repository | 架构师、开发 | 这个服务内部怎么组织? | 按需 |
| L4 Code | 单个 Component 内部的类/接口 | 开发 | 这个模块的代码结构是什么? | 几乎不手画,自动生成 |
实践建议:大多数团队只需要 L1 + L2。L3 按需,L4 用 IDE 自动生成。
“Container” 不是 Docker 容器
C4 中的 Container 是”可独立运行/部署的单元”,包括:
- Server-side Web App(Spring Boot 应用)
- SPA(React 应用)
- Mobile App
- Database schema(PostgreSQL 数据库)
- Message Queue(Kafka topic)
- Serverless Function
- 文件系统(S3 bucket)
判断标准:能不能独立部署?能 → Container;不能 → Component。
一个 Spring Controller 不是 Container(它不能脱离所在应用独立部署),而是 Component。
补充图类型
C4 除了核心四层,还有三种补充图:
| 图类型 | 用途 | 对应 |
|---|---|---|
| System Landscape | 在 L1 之上,展示组织级全部系统 | 企业架构鸟瞰 |
| Dynamic | 展示特定场景的交互时序(类似简化的序列图) | 用例级别的调用流 |
| Deployment | 展示 Container 到基础设施的映射(服务器 / K8s / CDN) | 运维视角 |
标记规则
C4 是 notation-independent 的 — 它定义抽象(Person / System / Container / Component),不定义具体的形状/颜色。但有约定:
每个元素必须包含
- 名称 — 这是什么
- 类型 — 明确标注 Person / Software System / Container / Component
- 描述 — 一句话说明职责
- 技术(Container/Component)— 用什么技术实现(如 “Java Spring Boot”、“PostgreSQL 15”)
关系箭头规则
- 单向箭头,表示依赖方向
- 必须标注 — “Uses” 不够,要具体:“Sends orders via JSON/HTTPS”
- Container 间通信必须标注协议(HTTPS / gRPC / AMQP / JDBC)
图级别规则
- 每张图必须有标题(类型 + 范围,如 “Container diagram for Internet Banking System”)
- 每张图必须有图例(legend)
工具生态
Model-driven vs Diagram-driven
| 方式 | 含义 | 代表工具 | 优点 | 缺点 |
|---|---|---|---|---|
| Model-driven | 定义一个模型,生成多个视图 | Structurizr DSL | 改名一次全局更新,不会不一致 | 学习成本稍高 |
| Diagram-driven | 每张图独立,元素可能重复 | PlantUML / Mermaid / draw.io | 简单直接 | 多图间容易不一致 |
Structurizr DSL(官方工具,Simon Brown 出品)
Structurizr 是 C4 的参考实现,model-driven 方式:
workspace "Internet Banking" {
model {
customer = person "Banking Customer" "A customer with bank accounts."
bankingSystem = softwareSystem "Internet Banking System" {
spa = container "SPA" "Customer-facing UI" "React"
api = container "API" "REST API" "Go, Gin" {
authController = component "Auth Controller" "Handles authentication" "Gin Handler"
orderService = component "Order Service" "Business logic" "Go"
}
db = container "Database" "Stores data" "PostgreSQL" {
tags "Database"
}
}
mainframe = softwareSystem "Mainframe" "Core banking" {
tags "External"
}
customer -> spa "Uses" "HTTPS"
spa -> api "Calls" "JSON/HTTPS"
api -> db "Reads/Writes" "SQL"
api -> mainframe "Gets account info" "XML/HTTPS"
}
views {
systemContext bankingSystem "Context" {
include *
autoLayout lr
}
container bankingSystem "Containers" {
include *
autoLayout
}
component api "API-Components" {
include *
autoLayout
}
styles {
element "Person" { shape person; background #08427b; color #fff }
element "Software System" { background #1168bd; color #fff }
element "Container" { background #438dd5; color #fff }
element "Database" { shape cylinder }
element "External" { background #999999 }
}
}
}
运行方式:
# Structurizr Lite(免费本地容器)
docker run -it --rm -p 8080:8080 \
-v $(pwd):/usr/local/structurizr structurizr/lite
# 导出为 PlantUML / Mermaid
docker run --rm -v $(pwd):/usr/local/structurizr structurizr/cli \
export -workspace workspace.dsl -format plantuml
C4-PlantUML
用 PlantUML 宏画 C4 图,diagram-driven 但语法清晰:
@startuml
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
LAYOUT_WITH_LEGEND()
Person(user, "Customer", "Places orders")
System_Boundary(sys, "E-Commerce System") {
Container(web, "Web App", "React", "SPA frontend")
Container(api, "API", "Go Gin", "REST API")
ContainerDb(db, "Database", "PostgreSQL", "Stores orders")
ContainerQueue(mq, "Message Queue", "Kafka", "Async events")
}
System_Ext(payment, "Stripe", "Payment processing")
Rel(user, web, "Uses", "HTTPS")
Rel(web, api, "Calls", "JSON/HTTPS")
Rel(api, db, "Reads/Writes", "SQL")
Rel(api, mq, "Publishes events", "Protobuf")
Rel(api, payment, "Charges", "HTTPS")
@enduml
核心宏:Person / System / System_Ext / Container / ContainerDb / ContainerQueue / Component / Rel / Rel_D / Rel_R。
Mermaid C4(实验性)
C4Context
Person(customer, "Customer", "A bank customer")
System(banking, "Banking System", "Online banking")
System_Ext(email, "Email System", "Sends notifications")
Rel(customer, banking, "Uses")
Rel(banking, email, "Sends emails", "SMTP")
支持 C4Context / C4Container / C4Component / C4Dynamic / C4Deployment,但标记为 experimental,不支持自定义图标和布局控制。
D2
D2 v0.7+ 支持 c4-person shape 和 C4 主题(theme-id: 200),可通过 suspend 机制实现 model-driven 的多视图效果。
draw.io
内置 C4 shape 库(More Shapes → Software → C4),拖拽使用。Diagram-driven,不支持模型共享。
C4 vs 其他方法
| 维度 | C4 | UML | Arc42 | 4+1 View Model | 随手画 |
|---|---|---|---|---|---|
| 学习成本 | 小时级 | 月级 | 天级 | 天级 | 零 |
| 抽象层次 | 四层递进 | 无层次要求 | 三层建筑块视图 | 五个正交视图 | 无 |
| 行为建模 | 弱(仅 Dynamic 图) | 强(序列/状态/活动图) | 运行时视图 | Process view | 无 |
| 形式化程度 | 无标准 | ISO/IEC 19501 | 无标准 | 无标准 | 无 |
| 团队采用率 | 高且增长 | 下降(学术/合规仍需要) | 中等 | 低 | 最高 |
| 工具链 | Structurizr + PlantUML + D2 + Mermaid | Enterprise Architect / StarUML | 模板 | 无标准工具 | 白板 |
C4 与 Arc42 高度互补:Arc42 第 5 章”Building Block View”的三层正好对应 C4 的 L2/L3/L4,很多团队同时使用两者。
C4 何时过度
- 极小项目(CLI 工具、静态网站)— README 够了
- 一次性原型 — 下个月就扔,不值得建模
- 硬件密集型系统 — C4 的抽象是软件特定的
C4 何时不够
- 复杂行为建模 — C4 Dynamic 图远不如 UML 序列图/状态图
- 合规行业要求形式化规约 — C4 无 ISO 标准
- 数百个系统的企业架构 — System Landscape 图太薄,需要 TOGAF/ArchiMate
Pitfalls
1. Container 和 Component 混淆
Spring Controller 不是 Container(不能独立部署),是 Component。判断标准永远是:能不能独立部署?
2. 在 C4 层级之间发明新层
“Subcomponent”、“Module”、“Subsystem” — 重新引入混乱。四层够了,硬往中间塞层只会破坏模型的简单性。
3. 展示外部系统的内部细节
外部系统应当是不透明的盒子。展示第三方 API 的内部数据库结构毫无意义 — 你不控制它,它随时可能变。
4. 关系标注含糊
“Uses” ← 废标注。应该写 “Sends order events via Kafka (Protobuf)” — 交互内容 + 协议 + 格式。
5. 缺少图例
即使用”标准” C4 配色(蓝色内部 / 灰色外部),也不能假定所有人都知道约定。每张图都要有 legend。
6. 试图在一张图里画所有东西
微服务架构下,一张 Container 图放 30 个服务 = 意面图。按业务域拆分为多张聚焦图。
7. 把共享库画成 Container
common-utils 这种共享库不是可部署单元,不应出现在 Container 图里。它被编译进使用它的 Container 中。
最佳实践
Diagram-as-Code 工作流
把 C4 源文件(Structurizr DSL / PlantUML / D2)放在代码仓库的 docs/architecture/ 中:
project/
docs/
architecture/
workspace.dsl # C4 模型
decisions/ # ADR(Architecture Decision Records)
001-use-postgresql.md
002-event-driven.md
src/
...
好处:Git 版本控制 → PR 审查架构变更 → CI 自动渲染图片 → 单一事实来源。
CI 自动渲染
# GitHub Actions
- name: Render C4 diagrams
run: |
docker run --rm -v $(pwd):/usr/local/structurizr \
structurizr/cli export \
-workspace docs/architecture/workspace.dsl \
-format plantuml
docker run --rm -v $(pwd):/data plantuml/plantuml-server:jetty \
java -jar /opt/plantuml.jar -tsvg docs/architecture/*.puml
从 L1 开始,按需展开
不要试图一次画完四层。起步只画 L1 + L2,L3 按需添加(复杂服务才需要),L4 用 IDE 生成。
保持图的鲜活
图过时的根本原因是更新成本太高。降低成本:
- 用 diagram-as-code(编辑文本比拖方框快)
- 只维护变化慢的层级(L1/L2 很少变)
- 把图的更新纳入架构评审流程,而不是事后补
创作者
Simon Brown — 独立软件架构咨询师,2006-2011 年间创建 C4 Model,著有 Software Architecture for Developers(Leanpub)和 The C4 Model(2024, O’Reilly)。创建了 Structurizr 作为 C4 的参考工具。
信息来源
- c4model.com — 官方站点(确定)
- Structurizr DSL 文档(确定)
- C4-PlantUML GitHub(确定)
- Mermaid C4 语法(确定,标记 experimental)
- Simon Brown GOTO 2024 演讲 “Misconceptions, Misuses & Mistakes”(确定)