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.sessionctx.headersctx.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. orgIdorgRoleprojectIdprojectRole 写回 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 链路