PlantUML 数据驱动 — 从数据库/API 实时生成图

puml.online

用 PlantUML + 几行脚本,让图自动跟着真实数据走。

为什么要数据驱动?

业务图最常见的尴尬:

  • 数据库加了字段,ER 图忘了同步,下游同学开始凭记忆写 join
  • 服务依赖变了,部署图却是三个月前的那张
  • 团队同学关系调整了,架构图还显示离职的人
  • 每周手动重画一遍——但凡少画一次就有人引用错误链接

思路:图的“事实”存在于代码 / 数据库 / API 文档 / 配置中心——而这些是真值(source of truth)。所以让图从这些真值“生成”出来——这种做法叫 data-driven diagram。

一个具体的例子:服务依赖图

假设我们有个 k8s namespace,所有 Deployment 都有自己的 label app=xxx。我们想画图展示 app=a 依赖哪些 app=b

步骤 1:从 kubectl 抽出数据

1
2
3
4
5
6
kubectl get deployments -n prod -o json | jq '
[.items[] | {
name: .metadata.labels.app,
depends: (.metadata.annotations."depends-on" // "")]
}] | map(select(.name))
' > deps.json

输出类似:

1
2
3
4
5
[
{"name": "frontend", "depends": "backend,redis"},
{"name": "backend", "depends": "postgres,redis"},
{"name": "redis", "depends": ""}
]

步骤 2:写个 Node 脚本生成 puml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// scripts/gen-deps.js
import fs from "node:fs";
import { execSync } from "node:child_process";

const deps = JSON.parse(
execSync('kubectl get deployments -n prod -o json')
.toString()
.match(/\[.*\]/s)?.[0] ?? "[]"
);

// 简化:假装 deps 已被正确解析
const groups = {};
for (const d of deps) {
const layer = d.name.startsWith("db") ? "storage" :
d.name.startsWith("cache") ? "cache" : "app";
(groups[layer] ??= []).push(d.name);
}

let puml = "@startuml deps\nskinparam nodesep 30\nskinparam ranksep 40\n\n";
for (const [layer, names] of Object.entries(groups)) {
puml += `package "${layer}" {\n`;
for (const n of names) puml += ` component "${n}" as ${n}\n`;
puml += "}\n\n";
}
for (const d of deps) {
for (const dep of (d.depends || "").split(",").filter(Boolean)) {
puml += `${d.name} --> ${dep}\n`;
}
}
puml += "\n@enduml\n";

fs.writeFileSync("output/deps.puml", puml);
console.log("生成 deps.puml");

步骤 3:render + commit

1
2
3
4
5
node scripts/gen-deps.js
# 用 plantuml CLI 渲染成 SVG(或者走 plantuml.com / TeaVM)
plantuml -tsvg output/deps.puml -o output/
git add output/deps.svg
git commit -m "chore: update dependency diagram from prod"

结果:每天的 CI 里跑一次,图永远是最新的。

几个常见的“数据源 → 图”模板

场景 数据源 产出
服务依赖 k8s labels / apollo / consul component / package
DB ER pg_dump --schema-only entity/class
状态机 XState / status 表 state 状态机
审批流 飞书 / Worktile / Jira activity 图
团队结构 HR 系统 / LDAP class 成员图
接口契约 OpenAPI / Protobuf class 图

DB ER 的纯 bash 实现

1
2
3
4
5
pg_dump --schema-only -t "*" mydb \
| grep -E "CREATE TABLE|^ \"\\w+\"" \
| awk '/CREATE TABLE/ {table=$3; next} /\"/ {print table, $0}' \
> tables.tsv
# 然后 awk 转 entity/relationship puml,略

集成建议

1. 输出尽量“small data”,别 raw DB

把 SQL / kubectl / API 的输出先用脚本压成 扁平 JSON(像上面的 deps.json),puml 生成脚本只读 JSON,不读 DB。

好处

  • puml 生成脚本 0 依赖、纯文本、好 PR review
  • 数据源变化时(比如换 DB),只改「抽数据脚本」,puml 不动

2. 把 puml 生成脚本放在仓库里

scripts/gen-*.puml.jspackage.json 一起提交。README 里写一行:

图是从生产数据自动生成的。改图?请改数据源(k8s labels / DB schema)。

3. CI 里跑“渲染验证”

每次 build 后跑:

1
2
3
node scripts/gen-deps.js
plantuml -tsvg output/deps.puml -o output/
git diff --exit-code output/deps.svg || (echo "图过时了" && exit 1)

如果数据源变了但 puml 脚本没改 → build 失败 → 提醒人去更新图。

踩过的坑

  • 循环依赖:A→B→A 会让 Graphviz 报错。puml 生成脚本里加 if seen.has(a+b) 短路
  • 节点太多:>100 个 component 时 SVG 会卡。拆分到 package 包里、按 layer 分页
  • 中文乱码:puml 生成的 SVG 在 GitHub inline 显示时中文会变方块。puml 顶部加 skinparam defaultFontName "Noto Sans CJK SC"
  • 敏感信息:从 prod 抓的依赖图可能含内部 hostname。puml 输出前记得脱敏(hash 或代号)

小结

  • 图应该跟着真值走——真值是谁,谁就是 source
  • 抽数据生成 puml 拆开两个脚本,便于维护
  • CI 里强制 git diff --exit-code 让图“不更新”就编译失败

下一步

  • 标题: PlantUML 数据驱动 — 从数据库/API 实时生成图
  • 作者: puml.online
  • 创建于 : 2026-07-28 16:35:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-data-driven/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。