C4 模型与架构文档化实践

C4 模型(Context/Container/Component/Code)四层抽象的架构文档化方法:每层该画什么、Structurizr 与 PlantUML 的图即代码实践、动态与部署视图扩展、与 ADR 的配合、让文档不过时的活文档流程,以及常见误区与选型建议。

架构图最大的问题不是画不出来,而是画完就过时:UML 图太复杂没人看,随手画的框图又说不清细节,新人和外部团队永远在"问人"而不是"看图"。C4 模型(C4 Model)用四个层层递进的抽象层级解决了"画多细"的问题,而"图即代码(Diagrams as Code)“解决了"会不会过时"的问题。本文讲清 C4 的四层、用什么工具落地、以及如何让它跟代码一起演进。

1. 为什么架构图总是失效

1.1 两个极端

架构文档常见的失败模式只有两种:

失败模式表现后果
画得太细一张 UML 类图塞进 200 个类没人看得懂,画完即废
画得太粗一个方框写"系统”说不清边界、依赖、职责
手工维护Visio/PPT 手绘代码改了图不改,迅速失真

根因是缺少"分层"意识:给 CEO 看的图和给新入职工程师看的图,粒度需求天差地别,硬塞进一张图必然两头不讨好。

1.2 文档化的正确目标

架构文档的目标不是"完整",而是让不同读者在合适的抽象层级上快速建立正确心智模型。这需要回答三个问题:

  • 这个系统是什么、为谁服务(对外);
  • 它由哪些可独立部署的单元组成、如何通信(对开发);
  • 某个容器内部如何拆成组件(对具体模块负责人)。

C4 模型正是为这三个问题设计的,同时呼应了 架构设计基础原则 中"关注点分离"的思路。


2. C4 模型:四层抽象

2.1 四层概览

C4 由 Simon Brown 提出,四个层次像地图的缩放级别:

层级名称面向读者元素典型数量
L1Context(上下文)所有人、非技术人、你的系统、外部系统5~10
L2Container(容器)技术/运维应用、数据库、队列、前端5~15
L3Component(组件)架构师/开发容器内的组件5~20
L4Code(代码)该模块开发者类/接口按需,通常省略

关键约定:C4 的"容器(Container)“指可独立运行/部署的单元(进程、服务、数据库、SPA),不是 Docker 容器——这是最常见的误解。

2.2 L1 上下文图:边界与外部依赖

上下文图回答"我们的系统在世界的哪个位置”。它只画三类东西:人、被描述的系统、与之交互的外部系统。

   ┌──────────┐         ┌───────────────┐
   │  顾客     │────────▶│   电商平台      │
   └──────────┘  下单    │  (被描述系统)   │
                        └───────┬───────┘
   ┌──────────┐                 │
   │ 运营人员  │─────────────────┘
   └──────────┘   管理后台
                        │ 支付
                        ▼
                  ┌───────────┐
                  │ 支付网关   │  ← 外部系统
                  └───────────┘

图上每个箭头都要标注交互内容(“下单"“支付”),不写技术细节(不写 HTTP/REST)。

2.3 L2 容器图:可部署单元与通信

容器图把被描述的系统"拆开”,展示其中的可独立部署单元及其通信协议:

   ┌────────────┐  HTTPS/JSON  ┌──────────────┐
   │  Web SPA    │─────────────▶│  API 网关     │
   │ (浏览器)    │              │ (Node/Express)│
   └────────────┘              └──────┬───────┘
                                      │ gRPC
                          ┌───────────▼──────────┐
                          │   订单服务            │
                          │   (Java/Spring)       │
                          └───┬──────────┬───────┘
                    JDBC      │          │ 发布事件
                 ┌────────────▼──┐   ┌───▼─────────┐
                 │ 订单数据库      │   │ Kafka       │
                 │ (PostgreSQL)   │   │ (事件总线)   │
                 └────────────────┘   └─────────────┘

这是信息密度最高的一层,多数团队的文档化精力应该集中在这里:它直接对应"部署拓扑 + 技术选型 + 集成方式"。

2.4 L3 组件图与 L4 代码图

组件图放大某个容器,展示内部组件与职责(如订单服务里的 OrderController、OrderService、OrderRepository)。L4 代码图(类图)通常不必手画——现代 IDE 能按需生成,且变化太快,手工维护得不偿失。

经验法则:手工维护到 L3 为止,L4 交给工具按需生成。


3. 图即代码:让文档跟着代码走

3.1 为什么用文本描述架构

手绘图过时的根因是它不在版本控制里、也不随代码评审。把架构描述成文本,就能享受代码的一切待遇:Git 版本化、PR 评审、CI 校验、可 diff。这就是"图即代码(Diagrams as Code)"。

3.2 Structurizr DSL

Structurizr 是 C4 的官方工具,用一套 DSL 同时定义模型与视图,多视图从同一个模型生成,保证一致性:

workspace "电商平台" {

  model {
    customer = person "顾客" "下单购买商品"
    ops      = person "运营人员" "管理商品与订单"

    ecommerce = softwareSystem "电商平台" {
      spa      = container "Web SPA" "React 单页应用" "浏览器"
      gateway  = container "API 网关" "路由与鉴权" "Node.js"
      orderSvc = container "订单服务" "订单与库存" "Java/Spring"
      orderDb  = container "订单数据库" "" "PostgreSQL"
      bus      = container "事件总线" "" "Kafka"
    }

    payment = softwareSystem "支付网关" "外部支付服务" "External"

    customer -> spa "使用"
    ops      -> spa "管理"
    spa      -> gateway "调用 API" "HTTPS/JSON"
    gateway  -> orderSvc "转发" "gRPC"
    orderSvc -> orderDb "读写" "JDBC"
    orderSvc -> bus "发布领域事件" "Kafka 协议"
    orderSvc -> payment "发起支付" "HTTPS"
  }

  views {
    systemContext ecommerce "context" { include * ; autoLayout lr }
    container ecommerce "containers" { include * ; autoLayout tb }
  }
}

从这一份模型可以同时导出上下文图、容器图、组件图,甚至生成可点击的架构文档站点。模型改了,所有视图同步更新——这是它对手绘图最大的优势。

3.3 PlantUML / Mermaid 作为轻量替代

如果不想引入 Structurizr,用 PlantUML 的 C4-PlantUML 宏或 Mermaid 也能画:

graph TB
  customer[顾客] --> spa[Web SPA]
  spa -->|HTTPS| gateway[API 网关]
  gateway -->|gRPC| order[订单服务]
  order --> db[(订单数据库)]
  order --> bus[[Kafka]]
工具优点缺点适用
Structurizr模型驱动、多视图一致学习成本、需渲染服务中大型系统
C4-PlantUML生态成熟、文本可 diff模型与视图仍需分别维护一般团队
MermaidGitHub/GitLab 原生渲染布局控制弱、元素多会乱轻量/内嵌文档
draw.io所见即所得、上手快文本 diff 差临时沟通

4. 扩展视图:C4 之外的补充

四层静态结构图不足以描述系统。Simon Brown 后来补充了几种视图:

4.1 动态视图

动态视图描述一次交互的时序(下单流程跨哪些容器、按什么顺序):

顾客 → SPA → 网关 → 订单服务 → 支付网关 → 订单服务 → Kafka
      (每个箭头标注序号 1..n 与内容)

它对应 UML 时序图,但只使用 C4 的元素,避免引入新概念。

4.2 部署视图

部署视图把容器映射到基础设施节点(可用区、K8s 集群、区域),回答"东西跑在哪里":

容器生产节点副本数备注
API 网关K8s prod-ns3HPA 2~10
订单服务K8s prod-ns4跨 3 可用区
订单数据库RDS 主备1 主 1 备多可用区

4.3 与 ADR 配合

C4 回答"系统长什么样",架构决策记录(ADR) 回答"为什么长这样"。两者是互补的:容器图上的每条连线背后都可能对应一条 ADR。

容器图:订单服务 ──发布领域事件──▶ Kafka
ADR-007:订单与库存解耦采用事件驱动而非同步 RPC

推荐做法:在 C4 元素上挂链接,指向对应的 ADR,让读者能一路从"是什么"追到"为什么"。


5. 落地:让文档不过时的流程

5.1 纳入 CI

把架构模型文件放进代码仓库,并在 CI 中校验:

# Structurizr CLI:校验模型 + 导出站点
structurizr-cli export -w docs/architecture/workspace.dsl \
  -f static-site -o build/arch-site

# 校验模型一致性(引用了不存在的元素会报错)
structurizr-cli validate -w docs/architecture/workspace.dsl

模型语法错误、引用了不存在的元素,CI 直接失败——这就把"文档"变成了"代码"。

5.2 谁在什么时候更新

触发时机更新内容责任人
新增/下线服务容器图提交 PR 的团队
引入新外部依赖上下文图架构师
模块重构组件图模块负责人
部署拓扑变化部署视图SRE/运维
重大选型决策关联新 ADR决策发起人

5.3 文档化的"够用"标准

不要追求大而全。一套可用的最小架构文档集:

  • 1 张上下文图(所有人能看懂边界);
  • 1 张容器图(开发能看懂拓扑);
  • N 张组件图(按需,只画复杂模块);
  • 关联的 ADR 库(解释关键决策);
  • 1 个 README 索引(说明每份文档的读者与维护人)。

6. 常见误区

误区表现修正
把"容器"当 Docker图里画了一堆 Pod容器 = 可独立部署单元
上下文图写技术细节箭头标"REST/JSON"只写业务交互
每层都手画L4 类图天天过时L4 交给 IDE 生成
图不标交互内容只有连线没有说明每条边标注做什么
文档脱离代码库放在 Confluence 里腐烂放仓库、进 CI、随 PR 评审
只有图没有决策新人问"为什么这样"图挂 ADR 链接
追求一张图讲完元素爆炸没人看分层,按读者拆分

还有一条常被忽略:别把 C4 当教条。小系统可能只需要上下文图 + 容器图两张;大系统可以在 L2 与 L3 之间加"子系统"层级。C4 提供的是分层思维,而非必须四层齐全的规范,其演进理念与 演进式架构 一致——文档结构应随系统演进调整。


7. 小结

要点结论
核心思想用四层抽象匹配不同读者的粒度需求
L1/L2上下文图讲边界,容器图讲拓扑,信息密度最高
L3/L4组件图按需画,代码图交给工具生成
防过时图即代码 + 进版本控制 + CI 校验
补视图动态视图讲时序,部署视图讲落地
配合C4 讲"是什么",ADR 讲"为什么",互相挂链

一句话记住:C4 的价值不在"四层",而在"分层"与"单一模型多视图"。当架构描述变成随代码演进的文本资产、当每张图都能追溯到对应的决策记录,架构文档才真正从"画完就废"变成团队的活知识库。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「架构」更多文章

  1. 韧性工程与错误预算:从 SLO 到故障演练
  2. 微前端架构:组合、隔离与独立部署
  3. 数据网格(Data Mesh):领域数据产品与去中心化治理