C4 系统上下文图(System Context):完整指南与示例
如果你这辈子只画一张架构图,那就画一张系统上下文图(System Context)。它是 C4 模型的入口,是每个干系人无需培训就能读懂的图,也是暴露团队对"系统到底做什么"分歧的最快方式。
然而大多数团队都画错了。他们把数据库、微服务、Kubernetes 集群一股脑塞进去,直到这张"上下文"图变成一张无法辨认的基础设施地图。或者他们干脆跳过它,直接画容器图(Container),让非技术干系人面对一张完全看不懂的图。
本指南是对 C4 模型第 1 层的深入剖析:什么是系统上下文图,它究竟应该包含什么(以及不该包含什么),一个完整的实战示例,毁掉大多数上下文图的常见错误,以及一套创建它的分步流程——无论是手工绘制还是从代码库生成。如果你对 C4 模型本身还不熟悉,请先阅读我们的 C4 模型完整指南,再回到这里。
什么是系统上下文图?
系统上下文图是 C4 模型中的第一张、也是最高层级的图。它把整个软件系统画成一个方框,回答唯一一个问题:这个系统如何融入它所处的环境?
正如术语表中的定义所说,系统上下文图聚焦于与被描述系统交互的人员和外部系统,刻意省略内部结构。它的目标是确立范围:谁使用这个系统,它依赖哪些系统,又有哪些系统依赖它。
上下文图上只出现三种元素,且仅有这三种:
- 你的软件系统——一个方框,位于中心。不是它的服务,不是它的数据库。就一个方框。
- 人员——与系统交互的用户、角色或人物画像(persona)。比如"客户"、"管理员"、"客服专员"。
- 外部软件系统——你的系统会与之通信、但并不归你团队拥有的系统:支付网关、邮件服务商、遗留 ERP、身份提供方。
连接它们的是关系:带标签的箭头,描述谁做了什么。"下单使用"、"发送支付请求至"、"接收来自……的物流更新"。
这就是全部词汇。系统、人员和外部系统用方框表示,关系用箭头表示。这张图的纪律恰恰来自你所省略的东西。
受众是谁?
系统上下文图在 C4 的四个层级中独一无二,因为它是为所有人设计的——包括那些永远不会读一行代码的人:
- 非技术干系人。 产品经理、CEO 或合规官看一眼上下文图,就能在一分钟内理解系统的目的、它的用户以及它的第三方依赖。
- 新团队成员。 在一位新工程师学习你的目录结构之前,他应该先了解你的系统边界。上下文图就是入职培训的第一张幻灯片。
- 架构师与安全评审人员。 每一条跨越系统边界的箭头都是一个集成点、一条数据流、一处潜在的攻击面。安全与合规评审往往从这里开始。
- 其他团队。 当另一个团队问"你们的系统会直接调用计费 API 吗?"时,上下文图无需开会就能给出答案。
这正是为什么技术选型不属于这里。当你在上下文图上写下"PostgreSQL"或"Kafka"的那一刻,你就把受众缩小到了工程师——而那些内容应该交给容器图。
上下文图该包含什么(以及不该包含什么)
应当包含
- 范围内的系统——恰好一个方框,命名清晰。
- 每一类用户——不是具体的某个人,而是角色或人物画像。如果客户和仓库人员以不同方式使用系统,他们就是两个独立的人员方框。
- 每一个外部系统依赖——包括那些你习以为常的:邮件服务、身份提供方、分析平台,以及在架构上相关的监控栈。
- 依赖你的系统——如果某个合作伙伴的系统消费你的 API,它就应该出现在图上,箭头指向你。
- 带标签的关系——每条箭头都需要一个动词短语。一条没有标签的箭头就是一个问号。
应当排除
- 容器——没有 Web 应用、没有 API 服务、没有数据库、没有消息队列。那些属于第 2 层。
- 技术选型——没有"React"、没有"Go"、没有"PostgreSQL"。上下文图应当在你彻底重写技术栈之后依然不变。
- 外部系统的内部结构——Stripe 就是一个方框,而不是一张 Stripe 架构图。
- 基础设施与部署细节——没有区域、没有集群、没有负载均衡器。
- 单个功能——这张图展示的是关系,而不是功能清单。
一个简单的检验:如果移除某个元素,既不会改变谁与系统交互,也不会改变它接触哪些外部系统,那这个元素就不属于第 1 层。
一个完整的上下文图示例:在线支付平台
我们来走一遍一个真实的例子。假设你要为 PayFlow 编写文档,这是一个让商户在自家网站上接受银行卡付款的在线支付平台。
第 1 步:为系统命名
中心放一个方框:PayFlow 支付平台。给它一句话描述:"让商户接受并管理在线银行卡付款。"
第 2 步:识别人员
谁与 PayFlow 交互,以什么角色?
- 商户(Merchant)——配置支付页面、查看交易、发起退款的企业主。
- 终端客户(End Customer)——通过 PayFlow 收银台为某次购买付款的消费者。
- 客服专员(Support Agent)——调查争议交易的内部人员。
第 3 步:识别外部系统
PayFlow 依赖什么,又有什么依赖 PayFlow?
- 银行卡网络 / 收单行——授权并清算银行卡交易。
- 欺诈检测服务——为交易评估欺诈风险分数。
- 邮件服务——发送收据和通知。
- 身份提供方——处理商户单点登录。
- 商户电商网站——商户自己的网站,它嵌入 PayFlow 收银台并消费 PayFlow API。
- 会计平台——接收清算报告以供商户记账。
第 4 步:画出关系
完整的元素与关系清单如下所示:
People:
[Merchant] --> [PayFlow] : Configures payments, views transactions, issues refunds
[End Customer] --> [PayFlow] : Pays for purchases via hosted checkout
[Support Agent] --> [PayFlow] : Investigates and resolves disputes
External systems:
[Merchant E-Commerce Site] --> [PayFlow] : Initiates payments via API, embeds checkout
[PayFlow] --> [Card Networks / Acquiring Bank] : Authorizes and settles card transactions
[PayFlow] --> [Fraud Detection Service] : Requests risk scores for transactions
[PayFlow] --> [Email Service] : Sends receipts and payment notifications
[PayFlow] --> [Identity Provider] : Delegates merchant authentication
[PayFlow] --> [Accounting Platform] : Exports settlement reports
总共十个元素:一个系统、三个人员、六个外部系统。每条箭头都有动词短语。这里没有提到任何服务、数据库、队列或编程语言——然而任何人读到这张图,都能明白 PayFlow 是什么、谁在用它、它依赖什么。
注意这张图立刻让以下几点变得一目了然:
- 合规面。 图上的银行卡网络和欺诈检测服务,向安全评审人员精确地指出了 PCI 相关数据流向何处。
- 供应商风险。 六个外部依赖,每一个都是潜在的故障点或合同谈判点。
- 双向边界。 商户的电商网站会调入 PayFlow——这个系统不只是其他服务的消费者,它本身也是别人的依赖。
这正是上下文图被设计来引发的那种讨论。
系统上下文图中的常见错误
错误 1:细节太多
最常见的失败。图一开始很干净,然后有人加了"就主数据库吧",接着是 API 网关,再接着是三个核心微服务,一个月之内它就变成了一张挂着上下文图名字的容器图。解决之道毫不留情:你的系统就是一个方框。如果你忍不住想展示内部结构,那正是该创建一张独立、互相链接的容器图的信号——而不是把这张图塞满。
错误 2:遗漏外部依赖
团队总是忘掉那些他们觉得无聊的依赖:邮件服务商、短信网关、身份提供方、CDN、功能开关服务。但正是这些"无聊"的依赖会导致真实的宕机和真实的供应商锁定。把你的配置文件和环境变量过一遍——每一个 API 密钥通常都对应一个应当出现在图上的外部系统。
错误 3:混淆抽象层级
一张展示"客户 → Web 应用 → 订单 API → PostgreSQL → Stripe"的图,把一个人、两个容器、一个数据库和一个外部系统混在了同一张扁平视图里。读者根本分不清你的系统边界在哪。每张 C4 图都应该恰好停留在一个缩放层级;C4 模型层级体系的全部意义就在于,你在图之间放大,而绝不在一张图之内放大。
错误 4:关系没有标签或含糊不清
"使用"不是一个关系标签——任何东西都"使用"任何东西。写出真正发生的事:"通过……提交订单"、"接收来自……的 webhook 事件"、"向……进行认证"。这些标签才是图中绝大部分信息的所在。
错误 5:画一次就再也不更新
一张 2023 年的上下文图,如果还显示着你去年就迁走的遗留 CRM,那就是在主动误导。上下文图的变化频率比低层级的图低,但它们确实会变——每一次新的供应商集成,每一个废弃的合作伙伴 API。把这张图当作活文档,而不是项目启动时的一次性产物。
如何创建系统上下文图:分步指南
手工方式
- 为系统命名并描述。 一句话:它为谁、做什么?
- 列出用户角色。 头脑风暴出每一个与系统交互的独立人物画像。交互方式相同的角色合并;不同的拆开。
- 列出外部系统。 逐一检查集成、API 密钥、webhook、定时导出。包括那些调用你的系统,而不只是你调用的系统。
- 画出关系。 每个有意义的交互画一条箭头,每条都用动词短语标注。方向遵循主动方:是谁发起了这次交互?
- 与团队评审。 这是价值最高的一步。一次 30 分钟的评审总能可靠地暴露分歧:"等等,我们还在调那个服务吗?"、"合作伙伴什么时候开始直接打我们的 API 了?"这些争论本身就是交付物。
- 把它发布到人们能找到的地方——而不是埋在一个没人看的 wiki 页面里。
绘图本身用什么工具都行:白板、diagrams.net、Structurizr、带 C4 扩展的 PlantUML。瓶颈从来不是绘图——而是第 2、3、5 步,以及让结果随时间保持准确。
自动化方式:用 Archyl 生成上下文图
这正是 Archyl 与绘图工具分道扬镳的地方。你不是从一张空白画布起步,而是连接你的代码仓库,运行 AI discovery(AI 发现):Archyl 分析你的代码结构、配置文件和依赖图谱,然后提议一份 C4 模型草稿——系统、外部依赖、容器、组件,以及它们之间的关系。你审阅每一条建议,接受或调整它,系统上下文视图便会从模型中自动渲染出来。
因为这张图是从模型生成的,而非手工绘制,于是有两件事随之而来:
- 各层级保持连通。 你上下文图上的系统方框,就是你在容器视图中钻取进去的同一个实体。图与图之间不会出现复制粘贴的漂移。
- 陈旧程度变得可度量。 Archyl 的漂移检测会持续地将你记录在案的架构与代码库中实际存在的内容做比对,于是当某个集成被移除、某个服务被重命名时,你是从一个漂移分数中得知的,而不是从一位一头雾水的新员工那里。
如果你更偏好让文档存活在版本控制中,Archyl 还支持"架构即代码(Architecture as Code)"工作流:用 YAML 定义你的 C4 模型,把它和代码一起提交,让图从这份定义中渲染出来。
从上下文到容器
系统上下文图划定了边界;下一层则打开这个方框。一旦你的上下文图稳定下来,自然的下一步就是容器图——这张第 2 层视图展示你系统内部的可部署单元(Web 应用、API、数据库、消息中间件)以及它们如何通信。我们在 C4 容器图指南 中对此有详细讲解。
这个推进顺序很重要。跳过上下文图、直接从第 2 层开始的团队,往往会画出边界模糊的容器图——外部系统和内部服务混作一团,因为从来没人就系统在哪里结束达成过一致。
常见问题
系统上下文图和容器图有什么区别?
上下文图(第 1 层)把你的系统画成一个方框,聚焦于它的环境:用户和外部系统。容器图(第 2 层)则放大到那个方框内部,展示可部署单元——Web 应用、服务、数据库——以及它们的技术选型。上下文图回答"这个系统与什么交互?";容器图回答"这个系统由什么构成?"
一张上下文图应该有多少个元素?
没有硬性规则,但大多数健康的上下文图都是一个系统、两到五个用户角色、三到十个外部系统。如果你超过了十五或二十个元素,要么是系统边界划得太宽,要么你是在记录一整片企业级图景——在那种情况下,C4 系统全景图(System Landscape,在一张视图中展示多个系统)才是更合适的选择。
数据库应该出现在系统上下文图上吗?
不应该——只要这个数据库属于你的系统,它就是一个内部细节,应当出现在容器层级。例外情况是某个由其他团队或供应商运营、而你的系统直接读取的数据库;它实质上就是一个外部系统,可以作为外部系统出现。
C4 上下文图和 UML 或数据流上下文图是一回事吗?
从概念上说它们是表亲——"上下文图"(一个系统及其环境)这个想法早于 C4,存在于结构化分析和 UML 实践之中。C4 版本的独特之处在于它在层级体系中的位置:图上的每个元素都能被分解到下一层,从而给你一张可导航的地图,而不是一张孤立的图片。
准备好创建你的系统上下文图了吗?免费试用 Archyl,直接从你的代码仓库生成 C4 模型。或者继续阅读:什么是 C4 模型?完整指南 | C4 容器图指南 | 术语表中的系统上下文图。