Skip to content

3.2 分层图解

学习目标

完成本节后,你将能够:

  1. 按“平面”和“链路”阅读架构图。
  2. 区分控制面、数据面、契约面、执行面和状态面。
  3. 用图解释组件为什么这样组合。

3.2.1 不要把图读成目录树

架构图里的 websharedworker 看起来像三层目录,但真正的边界是运行职责:

  • web 负责同步入口和控制面;
  • shared 负责跨运行时契约;
  • worker 负责异步执行;
  • Postgres/ClickHouse/Redis/S3 负责不同类型的状态。

如果只按目录读,会误以为 web -> shared -> worker 是普通函数调用。实际不是。webworker 的运行时连接点是 Redis/BullMQ job,类型连接点是 packages/shared/src/server/queues.ts

3.2.2 图例升级

图中元素架构含义Langfuse 例子
外部入口系统边界外的调用者Browser、SDK、OTel、API client
同步入口必须在 HTTP 请求内完成的控制逻辑tRPC、Public API、ingestion API
契约中心多运行时共享的规则Zod schema、queue payload、FilterState、field map
异步执行可重试、可限速、可批量的后台处理ingestion worker、eval worker、export worker
状态存储持久化或临时状态Postgres、ClickHouse、Redis、S3
实线主路径UI 查询、API 调用、ingestion 写入
虚线条件路径secondary queue、dual-write、legacy fallback、feature/env gate

3.2.3 按五条线读图

每条线都要回答三件事:

  1. 入口是谁:HTTP route、tRPC procedure、queue processor 还是 query builder。
  2. 契约在哪里:Zod schema、queue payload、filter state、field set 还是 DB schema。
  3. 状态落哪里:Postgres、ClickHouse、Redis、S3 或外部系统。

3.2.4 三种边界最重要

运行时边界

webworker 是不同进程。web 不能假设 worker 立即执行,worker 也不能假设 producer 的本地 env 和自己完全一致。因此 ingestion job payload 里出现了 bucketPrefix 这种字段:producer 写 S3 时把绝对前缀放进 payload,consumer 不再自己猜路径。

这类字段不是“冗余”,而是跨进程一致性设计。

租户边界

Langfuse 是多租户系统。无论 tRPC、Public API 还是 ClickHouse query,都必须把 project/org scope 带进去。protectedProjectProcedure 会从 input 里读 projectId 并写入 ctx.session.projectId;Public API wrapper 从 API key 推出 project scope;query builder 和 repository 负责把 project filter 推到数据层。

漏掉租户边界,是读这类 repo 时最危险的问题。

存储边界

同一个用户动作可能触发多个存储:

  • Postgres 保存组织、项目、session metadata、prompt 等事务状态;
  • ClickHouse 保存 trace、observation、score、events 等分析状态;
  • Redis 保存 BullMQ job、短期去重和调度状态;
  • S3/blob 保存 raw payload 和大对象。

读链路时不要只问“写到哪个表”,还要问“这个状态为什么不放到另一个存储”。

3.2.5 shared 的位置怎么理解

shared 在图中央,是因为它把“编译时依赖”和“运行时通信”隔离开:

问题如果没有 shared当前做法
queue payloadproducer/consumer 各自定义,滚动部署容易漂移queues.ts 统一 QueueName、QueueJobs、payload type
filter 语义UI、API、SQL 各自理解 operatorFilterState 和 query builder 共同解释
domain schemaroute、worker、repository 各自校验domain Zod schema 统一输入输出形状
DB client每层自己创建连接和 error 类型shared server package 提供 client、repository、error

所以 shared 的设计重点不是“减少重复代码”,而是“让跨边界协议有唯一来源”。

3.2.6 看图时的自检问题

读任意新增功能时,把它放回图上:

  1. 它从哪个入口进入?
  2. 它是否需要同步返回?
  3. 它是否会进入队列?
  4. 它的 payload/schema 在哪里定义?
  5. 它写入哪个状态存储?
  6. 它的失败由 HTTP error、BullMQ retry、secondary queue、还是业务状态处理?
  7. 它会不会影响 Public API、SDK 或 generated client?

能回答这七个问题,才算看懂架构图。

下一节

目录结构