3.3 目录结构
学习目标
完成本节后,你将能够:
- 按运行职责而不是文件夹名字理解顶层目录。
- 知道做不同任务时应该从哪个入口开始追链路。
- 识别哪些目录是源码、哪些是契约源、哪些是生成物或构建产物。
3.3.1 目录树只是地图,不是架构
当前 repo 的顶层结构大致是:
text
langfuse/
├── web/ # Next.js app:UI + tRPC + Public REST + ingestion HTTP
├── worker/ # BullMQ consumers、后台任务、批量写入和副作用
├── packages/shared/ # 领域模型、队列契约、DB client、query builder、repository
├── ee/ # Enterprise package,被 web 消费
├── generated/ # 生成的 API clients,不手改
├── fern/ # Public API 定义源
├── scripts/ # repo 维护脚本
└── docs/ # 本书站点和中文架构图但是这棵树不能直接等同于架构。更准确的读法是:
web是同步入口层,包含浏览器 UI、tRPC、Public API、ingestion HTTP 接收;worker是异步执行层,消费 Redis/BullMQ job;packages/shared是跨运行时契约层,让web和worker对同一 payload、schema、query 语义保持一致;fern是外部 API contract 的源头;generated是生成结果,只读;- Postgres、ClickHouse、Redis、S3 不在目录树里,但它们是架构的一部分。
3.3.2 web 目录怎么读
web 不是纯前端。它同时承担 UI 和同步服务端入口。
| 子区域 | 角色 | 先看文件 |
|---|---|---|
web/src/pages | Next.js Pages Router,包含 public API file routes | web/src/pages/api/public/** |
web/src/server/api | tRPC 工厂、context、middleware、router 聚合 | root.ts、trpc.ts |
web/src/features/* | 以产品能力组织 UI、hooks、server router、service | features/events/server/eventsRouter.ts |
web/src/components | 跨 feature UI 组件和表格抽象 | components/table/** |
web/src/styles | Tailwind v4 theme、CSS variables、颜色 token | globals.css |
web/src/features/public-api | Public API wrapper、auth、rate limit、types | server/createAuthedProjectAPIRoute.ts |
web/src/features/search-bar | v4 search grammar、adapter、URL/filter contract | README.md |
读 web 时要先判断入口类型:
这三条入口的鉴权、错误格式和稳定性都不同。不要把 Public API route 当成 tRPC 的另一种写法。
3.3.3 packages/shared 目录怎么读
packages/shared 的名字容易误导。它不是“公共 helper 集合”,而是系统契约中心。
| 子区域 | 角色 | 为什么重要 |
|---|---|---|
src/domain/** | Trace、Observation、Score、Prompt 等领域 schema | UI、API、worker、tests 都需要同一语义。 |
src/server/queues.ts | QueueName、QueueJobs、payload Zod schema、job type map | producer/consumer 跨进程共享协议。 |
src/server/redis/** | BullMQ queue classes、sharding、Redis helpers | producer 创建 typed queue,worker 注册对应 queue。 |
src/server/ingestion/** | request-side ingestion validation、S3 path、batch enqueue | SDK event 进入数据面的第一层 contract。 |
src/server/queries/** | ClickHouse query builders、filter lowering、field maps | UI filter 和 Public API 查询不能手写散落 SQL。 |
src/server/repositories/** | Postgres/ClickHouse repository 和 record schema | 写入/读取同构,处理 DB row 到 domain 的转换。 |
prisma/schema.prisma | Postgres schema 源头 | 事务状态和关系模型。 |
clickhouse/migrations/** | ClickHouse schema 源头 | 高吞吐事件和分析表。 |
判断一个新类型是否该进 shared,核心问题是:它是否被两个运行时共同理解?
如果只是一个 React component 的 props,留在 web。如果它会进入 Redis job、S3 path、ClickHouse row、Public API response、FilterState,就要按 contract 对待。
3.3.4 worker 目录怎么读
worker 的入口不是 HTTP route,而是 worker/src/app.ts 中的队列注册。
| 子区域 | 角色 | 先看文件 |
|---|---|---|
worker/src/app.ts | 根据 env 开关注册 BullMQ worker | 看哪些 queue 会在当前部署消费。 |
worker/src/queues/** | 每类 job 的 processor | 看 job 如何解析 payload、调用 service、处理失败。 |
worker/src/services/IngestionService | ingestion 业务转换核心 | trace/observation/score/dataset_run_item 的 merge、enrich、validate。 |
worker/src/services/ClickhouseWriter | ClickHouse 批量写入策略 | 按表缓冲、flush、retry、截断、dropped metrics。 |
worker/src/features/** | 后台清理、retention、metrics runner 等长期任务 | 看启动条件和失败语义。 |
worker/src/app.ts 的工作模式是:
所以读 worker job 时,顺序应该是:
packages/shared/src/server/queues.ts:job payload contract;- producer:谁调用
.add(QueueJobs...); worker/src/queues/**:consumer 怎么处理;worker/src/app.ts:是否注册、concurrency 和 limiter 是多少;- service:真正业务是否被抽出来复用;
- failure semantics:throw、retry、secondary queue、业务失败状态分别怎么处理。
3.3.5 fern、generated、ee 怎么看
| 目录 | 角色 | 修改规则 |
|---|---|---|
fern/apis/** | Public API 定义源 | 对外 API 变更要从这里同步。 |
generated/** | API client 或生成产物 | 不手改。改 contract 后重新生成。 |
ee/** | Enterprise package | 被 web 消费,仍依赖 @langfuse/shared。 |
Public API 不是只改 web/src/pages/api/public/**。如果请求、响应、错误或分页行为是外部用户可见的,就要同步 Fern 和生成物。
3.3.6 任务驱动的打开顺序
| 任务 | 第一入口 | 第二入口 | 第三入口 |
|---|---|---|---|
| 看 UI 列表查询 | web/src/features/events/server/eventsRouter.ts | eventsService.ts | event-query-builder.ts |
| 看 Public API | web/src/pages/api/public/** | withMiddlewares.ts / createAuthedProjectAPIRoute.ts | fern/apis/** |
| 看 ingestion 写入 | ingestion route | processEventBatch.ts | worker/src/queues/ingestionQueue.ts |
| 看 worker job | packages/shared/src/server/queues.ts | producer .add(...) | worker/src/app.ts + processor |
| 看筛选系统 | web/src/features/search-bar/README.md | packages/shared/src/interfaces/filters.ts | query builder / filter lowering |
| 看颜色系统 | web/src/styles/globals.css | component class usage | docs theme if改本书站点 |
3.3.7 不要手改这些目录
| 路径 | 原因 |
|---|---|
generated/* | 生成物。 |
web/.next/*、web/.next-check/* | Next.js 构建产物。 |
*/dist/* | package 构建产物。 |
packages/shared/prisma/generated/* | Prisma 生成物。 |
.vitepress/dist/* | 本书站点构建产物。 |
这些路径可以被构建命令重建。手改只会制造不可追踪差异。
3.3.8 本节总结
目录结构的正确读法是:
text
入口在哪里 -> 契约在哪里 -> 执行器在哪里 -> 状态在哪里 -> 失败如何恢复只有把目录放回运行链路里,才能看出 web、shared、worker 如何组合成一个系统。