跳转到正文
zeno's blog
返回

系统设计基础(一):C4 Model 如何用四层缩放解决架构图混乱

专题: 系统设计基础

Table of contents

Open Table of contents

TL;DR

C4 Model 借鉴地图学的缩放思想,用 System Context → Container → Component → Code 四层递进抽象,让不同受众在对应层次获取所需信息,解决传统架构图”一张图什么都画、什么都看不清”的问题。


传统架构图为什么失败

大多数架构图是白板上的”随意方框 + 箭头”,核心问题:

  1. 抽象层次混乱 — 同一张图里,一个方框可能是微服务,另一个是 Java 类,还有一个是数据库表,没人知道每个方框代表什么粒度
  2. 没有标准词汇 — “组件”这个词对不同人意味着 Spring Bean / Docker 容器 / npm 包 / DLL,沟通无效
  3. 受众错配 — 一张图试图同时服务 CEO、开发、运维,结果三方都看不懂
  4. 图即死亡 — 画完就过时,永远不更新,最终成为误导源
  5. 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 是”可独立运行/部署的单元”,包括:

判断标准:能不能独立部署?能 → Container;不能 → Component。

一个 Spring Controller 不是 Container(它不能脱离所在应用独立部署),而是 Component。


补充图类型

C4 除了核心四层,还有三种补充图:

图类型用途对应
System Landscape在 L1 之上,展示组织级全部系统企业架构鸟瞰
Dynamic展示特定场景的交互时序(类似简化的序列图)用例级别的调用流
Deployment展示 Container 到基础设施的映射(服务器 / K8s / CDN)运维视角

标记规则

C4 是 notation-independent 的 — 它定义抽象(Person / System / Container / Component),不定义具体的形状/颜色。但有约定:

每个元素必须包含

  1. 名称 — 这是什么
  2. 类型 — 明确标注 Person / Software System / Container / Component
  3. 描述 — 一句话说明职责
  4. 技术(Container/Component)— 用什么技术实现(如 “Java Spring Boot”、“PostgreSQL 15”)

关系箭头规则

图级别规则


工具生态

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 其他方法

维度C4UMLArc424+1 View Model随手画
学习成本小时级月级天级天级
抽象层次四层递进无层次要求三层建筑块视图五个正交视图
行为建模弱(仅 Dynamic 图)强(序列/状态/活动图)运行时视图Process view
形式化程度无标准ISO/IEC 19501无标准无标准
团队采用率高且增长下降(学术/合规仍需要)中等最高
工具链Structurizr + PlantUML + D2 + MermaidEnterprise Architect / StarUML模板无标准工具白板

C4 与 Arc42 高度互补:Arc42 第 5 章”Building Block View”的三层正好对应 C4 的 L2/L3/L4,很多团队同时使用两者。

C4 何时过度

C4 何时不够


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 生成。

保持图的鲜活

图过时的根本原因是更新成本太高。降低成本:

  1. 用 diagram-as-code(编辑文本比拖方框快)
  2. 只维护变化慢的层级(L1/L2 很少变)
  3. 把图的更新纳入架构评审流程,而不是事后补

创作者

Simon Brown — 独立软件架构咨询师,2006-2011 年间创建 C4 Model,著有 Software Architecture for Developers(Leanpub)和 The C4 Model(2024, O’Reilly)。创建了 Structurizr 作为 C4 的参考工具。

信息来源


分享这篇文章:

上一篇
Rust 泛型(一):总览
下一篇
Rust 基础:异步编程内部机制