1.3 源码拆解方法
学习目标
完成本节后,你将能够:
- 用 infra/平台型系统的视角读 Langfuse,而不是只按目录浏览。
- 区分控制面、数据面、执行面、契约面和状态面。
- 把外部方法论映射到本 repo 的具体文件。
1.3.1 为什么不能从目录树开始
大型 infra repo 的目录树通常不是系统架构本身。目录回答“代码放在哪里”,但不回答:
- 请求从哪里进入;
- 哪些组件只是入口,哪些组件真正执行业务;
- 哪些类型是跨进程契约;
- 哪些存储承载事务状态,哪些承载分析事件;
- 哪些路径是同步返回,哪些路径是异步重试;
- 系统在高吞吐、失败、重放、延迟写入时如何保持可理解。
Langfuse 就是这种系统。web 不是简单前端,worker 不是附属脚本,packages/shared 也不是工具箱。它们组合成一个“LLM observability 数据平台”:外部 SDK 和 UI 产生请求,web 做同步控制,shared 固化契约,worker 执行后台数据面,ClickHouse/Postgres/Redis/S3 分别承载不同状态。
1.3.2 先建立六个阅读镜头
这套镜头来自几类常见架构阅读方法:C4 的分层放大、Kubernetes 式 control plane / worker node 分工、SRE 对生产系统运行属性的关注、Well-Architected 对质量属性的追问、ADR 对设计取舍的记录方式,以及 Diátaxis 对教程内容组织的区分。
| 镜头 | 阅读问题 | Langfuse 里对应什么 |
|---|---|---|
| 系统上下文 | 谁在系统外部?谁调用它? | Browser、SDK、OTel collector、API client、LLM provider、webhook target |
| 容器/运行时 | 哪些进程在跑?谁同步,谁异步? | web、worker、Postgres、ClickHouse、Redis、S3/blob |
| 控制面 | 谁做鉴权、路由、限流、调度、开关? | tRPC procedure、Public API wrapper、WorkerManager.register、env flags |
| 数据面 | 高吞吐数据如何进入、转换、写入、查询? | ingestion API、S3 raw events、BullMQ、IngestionService、ClickhouseWriter |
| 契约面 | 哪些结构必须被多方共同遵守? | Zod domain、QueueName、QueueJobs、FilterState、query builder fields |
| 状态面 | 数据放在哪里,为什么放那里? | Postgres 元数据、ClickHouse 事件、Redis 队列/缓存、S3 原始事件 |
用这六个镜头读代码,比直接打开 web/src 更稳定。原因很简单:infra 系统的复杂度通常不在单个函数,而在“组件之间如何约定、失败时谁接手、状态如何跨进程流动”。
1.3.3 Langfuse 的阅读主线
这张图不是系统运行图,而是读代码顺序。先看上下文和运行时,知道有哪些外部入口和进程;再看控制面和数据面,知道每类请求如何被处理;随后看契约面,确认谁和谁共享规则;最后看状态面,理解为什么数据被拆到不同存储。
1.3.4 每个镜头对应的第一批文件
| 镜头 | 先打开 | 读到什么程度算懂 |
|---|---|---|
| 系统上下文 | part03-architecture/index.md、原 repo 的 web/src/pages/api | 能说出 UI、SDK、Public API、OTel 分别走哪条入口。 |
| 运行时容器 | pnpm-workspace.yaml、package.json、turbo.json | 能解释 web、worker、shared 的依赖方向和启动命令。 |
| 控制面 | web/src/server/api/trpc.ts、createAuthedProjectAPIRoute.ts、worker/src/app.ts | 能指出鉴权、限流、项目权限、队列注册在哪里完成。 |
| 数据面 | processEventBatch.ts、ingestionQueue.ts、IngestionService、ClickhouseWriter | 能从 SDK event 追到 ClickHouse insert。 |
| 契约面 | packages/shared/src/server/queues.ts、interfaces/filters.ts、domain schema | 能说出 producer 和 consumer 共享哪些结构。 |
| 状态面 | schema.prisma、ClickHouse migrations、Redis queue classes、S3 path helpers | 能解释每个存储承担的状态类型和失败语义。 |
不要一次读完所有文件。每轮只追一个链路,用这个链路反向验证架构边界。
1.3.5 场景驱动阅读
| 场景 | 阅读路线 | 关键判断 |
|---|---|---|
| UI 查询列表 | 页面/feature component -> tRPC router -> service/repository -> ClickHouse query builder | 是否把筛选语义保持在 FilterState 和 query builder 中。 |
| Public API | pages/api/public -> withMiddlewares -> createAuthedProjectAPIRoute -> service -> Fern | 是否维护外部契约、错误格式、分页和限流。 |
| ingestion 写入 | API route -> processEventBatch -> S3/Redis -> worker processor -> IngestionService -> ClickhouseWriter | 是否理解“请求侧只排队,写入侧再合并”的设计。 |
| worker job | queues.ts -> producer -> worker/src/queues/* -> worker/src/app.ts | queue name、job name、payload、processor 注册是否一致。 |
| 筛选语法 | search-bar README -> FilterState -> field map -> query builder | grammar、validate、lower、SQL 是否同构。 |
| 数据模型变更 | domain schema -> storage schema -> writer -> reader -> API/UI contract | 是否同时处理写入、读取、迁移和兼容。 |
1.3.6 读 infra repo 时必须追问的质量属性
代码“能跑”只是第一层。平台型系统还要回答:
| 质量属性 | 在 Langfuse 里怎么问 |
|---|---|
| 可靠性 | job 失败后谁 retry?哪些错误应该进入 secondary queue?哪些错误不能无限重试? |
| 可扩展性 | 高吞吐事件是否走批量写入?查询是否避免大字段和无界扫描? |
| 安全性 | project/org scope 在哪里校验?Public API key 与 session 鉴权是否分开? |
| 成本 | 为什么 raw event 放 S3、job 放 Redis、分析数据放 ClickHouse? |
| 可观测性 | span attributes、queue metrics、ClickHouse log comment 是否覆盖关键路径? |
| 兼容性 | queue payload、Public API response、ClickHouse schema 如何处理滚动部署和旧数据? |
这些问题会把教程从“文件介绍”推向“系统原理”。
1.3.7 反模式
- 目录漫游:从
web/src/components读起,会看到很多 UI 细节,但不知道请求归属哪条链路。 - 只看 route handler:会误以为 API route 做了全部事情,忽略 shared 契约和 worker 执行器。
- 只看 SQL:会错过筛选语法、tenant filter、field set、排序优化和大字段延迟读取。
- 忽略队列契约:producer 和 consumer 分属不同进程,payload 变更不是普通类型重命名。
- 忽略 env 开关:worker 里很多 processor 是否启动由配置决定,代码存在不代表运行时一定消费。
- 把 shared 当 utils:shared 里的类型会被多个运行时消费,放进去就意味着承诺了跨边界稳定性。
1.3.8 外部参考如何落到本书结构
本书后续章节会按这套方式组织:
- 第 3 篇用 C4 式层级解释系统上下文、运行时容器和组件组合。
- 第 4 篇用动态视角解释同步 API、异步 ingestion、worker、查询链路。
- 第 5 篇专门讲契约,因为 infra repo 的变化风险主要出现在跨边界协议上。
- 第 6 篇讲前端时不只讲 UI,而是讲 UI 状态如何进入 API 和查询契约。
- 第 7 篇讲二次开发时用任务路线图,而不是散列文件名。
参考
- C4 model
- Kubernetes Components
- Google SRE Book
- AWS Well-Architected Framework
- Architectural Decision Records
- Diátaxis