9.1 子系统精读模板
学习目标
完成本节后,你将能够:
- 用同一套结构拆解 Langfuse 的任意子系统。
- 区分“业务抽象”“数据契约”“运行链路”“存储落点”“查询入口”。
- 阅读子系统时避免只按目录树游走,而是从闭环和不变量出发。
9.1.1 为什么需要子系统精读
前面的章节已经讲过五层架构、运行链路、契约和数据结构。但真正读一个 infra repo 时,工程问题通常不是“这个目录是什么”,而是:
text
某个产品能力从配置到执行再到查询,完整经过哪些对象?
哪些字段是跨进程契约?
哪些表是事实数据,哪些表是配置数据?
worker 为什么这样拆队列?
如果我要做一个类似系统,哪些抽象值得复用?所以从这一篇开始,每个子系统都按同一套格式写。第一个完整示范是 Ingestion / Event Store 子系统,第二个示范是 Experiment 子系统,第三个示范是 Evaluation 子系统。
本篇后续章节统一使用两个标记:
| 标记 | 含义 |
|---|---|
| 【当前实现】 | 当前 Langfuse OSS repo 已经存在的表、代码路径、队列契约、运行链路或查询逻辑。 |
| 【扩展设计】 | 基于当前抽象推导出的改造建议、未来形态或“如果自己做类似 infra 可以怎么做”。 |
如果一段内容在讲“应该怎么改”“建议怎么做”“未来可以怎么扩展”,必须显式标为 【扩展设计】,不能让读者误以为当前 repo 已经实现。
9.1.2 固定拆解顺序
每个子系统都按下面 10 层拆:
| 层 | 要回答的问题 | 产出 |
|---|---|---|
| 业务闭环 | 这个子系统解决哪个真实业务问题? | 一句话结论和业务图。 |
| 核心对象 | 系统里有哪些名词,哪些容易混淆? | 术语表。 |
| 数据契约 | 外部输入、内部 payload、DB row 的格式是什么? | schema、JSON 示例、字段语义。 |
| 控制面 | 谁定义规则、配置、状态? | Postgres model、UI/API 入口。 |
| 数据面 | 事实数据从哪里来,怎么写入? | ingestion、worker、ClickHouse 表。 |
| 执行面 | 异步任务怎么触发、排队、重试? | QueueName、QueueJobs、processor。 |
| 查询面 | UI/API 如何读出结果,怎么聚合? | repository、query builder、tRPC router。 |
| 状态与幂等 | 如何避免重复执行、覆盖、旧 job 不兼容? | deterministic id、cache、status、不变量。 |
| 设计取舍 | 为什么不是更简单的一张表或一个同步函数? | 替代方案对比。 |
| 二次开发入口 | 新增能力应该改哪里,如何验证? | 文件清单和 checklist。 |
这个顺序很重要。先看目录树会陷入局部文件;先看闭环和契约,才能知道每个文件为什么存在。
9.1.3 三张图必须画
每个子系统至少应该有三张图:
对象关系图
回答“业务对象怎么组合”。
运行链路图
回答“一次请求或任务经过哪些进程”。
存储落点图
回答“什么放 Postgres,什么放 ClickHouse,什么放 Redis/S3”。
9.1.4 每节都要讲的四类“格式”
教程不是只讲概念,还要让读者能对着源码读。所以每个子系统都要列出四类格式:
| 格式 | 例子 | 为什么要写 |
|---|---|---|
| 配置格式 | JobConfiguration、ScoreConfig、feature settings | 看懂控制面如何定义行为。 |
| 输入格式 | public API body、SDK payload、queue payload | 看懂 producer/consumer 之间的协议。 |
| 中间格式 | S3 snapshot、worker internal object、domain object | 看懂为什么要多一步转换。 |
| 事实格式 | ClickHouse row、score record、event row | 看懂查询和分析为什么能成立。 |
如果一个章节没有这些格式,读者很容易只记住名词,不能自己实现类似 infra。
9.1.5 每节都要落到“不变量”
一个 infra 子系统最有价值的地方通常不是文件名,而是不变量。比如:
- 队列 payload 必须兼容 rolling deploy。
- 事实数据必须带 tenant/project 边界。
- worker 不能把用户请求同步阻塞在慢写入上。
- score 只能有一个主 target。
- experiment item score 和 experiment run score 是两个层级,不能混成一列。
不变量是二次开发时最不该破坏的东西。每个子系统章节最后都会总结这些规则。