Skip to content

4.2 tRPC 链路 ​

学习目标 ​

完成本节后,你将能够:

  1. 从 Browser UI 追到 tRPC procedure、service、repository 和数据层。
  2. 理解 protectedProjectProcedure 为什么是 UI 内部 API 的控制面核心。
  3. 判断 router、middleware、service、repository 的职责是否放错位置。

4.2.1 先给结论 ​

tRPC 链路是 Langfuse Web UI 的内部 API 通道。它的核心不是“类型安全调用”四个字,而是把 UI 请求放进一条受控管道:

text
Browser hook -> appRouter -> procedure middleware -> feature service/repository -> Postgres/ClickHouse

在这条链路里:

  • root.ts 负责聚合业务 router;
  • trpc.ts 负责 context、OpenTelemetry、错误处理、session 和 project/org/trace 访问控制;
  • feature router 负责把 input schema 和业务调用接起来;
  • service/repository 负责真正的数据读取、写入和转换;
  • shared query builder/repository 负责跨视图的查询语义。

4.2.2 链路图 ​

4.2.3 root.ts:router 聚合入口 ​

源码:web/src/server/api/root.ts

appRouter 把各 feature router 统一挂到 tRPC 根路由,例如:

  • events: eventsRouter
  • traces: traceRouter
  • scores: scoresRouter
  • dashboard: dashboardRouter
  • projects: projectsRouter
  • searchBar: searchBarRouter
  • v4Transition: v4TransitionRouter

这说明 UI 内部 API 的主入口不是某个 Next.js file route,而是 appRouter。当你在前端看到 api.events.all.useQuery(...),要去 eventsRouter 找对应 procedure。

4.2.4 trpc.ts:控制面工厂 ​

源码:web/src/server/api/trpc.ts

这个文件有四类关键职责。

Context ​

createTRPCContext 从 Next.js request/response 中拿到:

  • NextAuth session;
  • request headers;
  • Prisma client;
  • user 信息写入当前 span。

这让后续 procedure 可以访问 ctx.session、ctx.headers、ctx.prisma。

OpenTelemetry ​

withOtelInstrumentation 会读取 raw input,取出 projectId,再调用 contextWithLangfuseProps 设置:

  • headers;
  • userId;
  • projectId;
  • ClickHouse surface = trpc;
  • route = tRPC path。

这让后续 ClickHouse 查询和日志能知道自己来自哪条 UI route。

错误处理 ​

withErrorHandling 会统一处理 tRPC error:

  • ClickHouseResourceError 转成对用户更友好的 422;
  • 4xx 暴露安全 message;
  • 5xx 在 cloud/self-hosted 下返回不同提示;
  • stack 对敏感 ClickHouse error 会被 formatter 移除。

这说明 router 内部不要随意绕过 procedure 工厂,否则错误格式和可观测性会不一致。

Procedure 分层 ​

procedure作用
publicProcedure不要求登录,但仍接入 otel 和错误处理。
authenticatedProcedure要求用户登录。
protectedProjectProcedure要求登录,并从 input 中读取 projectId,验证 project membership。
protectedOrganizationProcedure要求登录,并验证 org membership。
protectedGetTraceProcedure保护 trace 详情读取,支持 public trace/session 的特殊访问规则。

最常见的 UI 项目级数据读取应该用 protectedProjectProcedure。

4.2.5 protectedProjectProcedure 的工作原理 ​

它做的不是简单 “is logged in”:

  1. 检查 session 是否存在;
  2. 从 raw input 解析 projectId;
  3. 在 ctx.session.user.organizations[].projects[] 中查 project membership;
  4. admin 用户走特殊路径,必要时从 Postgres 查询 project org;
  5. 调用 sendAdminAccessWebhook 记录 admin access;
  6. 把 orgId、orgRole、projectId、projectRole 写回 ctx.session;
  7. 后续 handler 只应该使用 ctx.session.projectId,而不是信任客户端传入的 input.projectId。

这个设计的重点是:tenant scope 在 middleware 中被标准化,后续业务逻辑不要重新发明权限判断。

4.2.6 以 v4 Events 列表为例 ​

源码:

  • web/src/features/events/server/eventsRouter.ts
  • web/src/features/events/server/eventsService.ts
  • packages/shared/src/server/queries/clickhouse-sql/event-query-builder.ts

eventsRouter.all 的链路是:

  1. protectedProjectProcedure.input(GetAllEventsInput) 校验输入;
  2. applyCommentFilters 把评论相关筛选转换成可查询条件;
  3. normalizeOrderByForTable 规范排序,避免和表的时间列不一致;
  4. getEventList 设置 selectIOAndMetadata: false,列表默认不取重字段;
  5. shared repository/query builder 生成 ClickHouse SQL;
  6. 另外读取 observation score 和 trace-level score;
  7. 返回给 UI 表格。

这个例子说明:router 不应该直接拼 SQL;它只负责输入、权限、少量 orchestration,然后交给 service 和 shared query 层。

4.2.7 tRPC 和 shared 的边界 ​

逻辑应该在哪里
session/project/org 权限web/src/server/api/trpc.ts procedure middleware。
feature-specific input schemafeature router 附近。
只给 UI 用的组合逻辑web/src/features/*/server/*Service.ts。
跨 UI/API/worker 的 domain schemapackages/shared/src/domain/**。
ClickHouse 字段映射、FilterState loweringpackages/shared/src/server/queries/**。
纯 worker 执行逻辑worker/src/**。

如果 router 里开始出现大量 SQL、queue payload 结构、ClickHouse insert、跨 worker 的状态处理,通常说明边界放错了。

4.2.8 自检清单 ​

  • input schema 是否包含 projectId,并且 procedure 会验证它?
  • handler 是否使用 ctx.session.projectId,而不是盲信 input?
  • router 是否只做 orchestration,没有塞大量业务转换?
  • 查询是否走 shared repository/query builder,而不是手写散落 SQL?
  • ClickHouse 查询是否有 project filter、时间窗口、field set 和 query tag?
  • 这个 API 是否只是 UI 内部使用?如果给外部用户用,应该走 Public API contract。

下一节 ​

Public API 链路