Skip to content

3.4 依赖方向

学习目标

完成本节后,你将能够:

  1. 区分 package import 依赖和运行时通信依赖。
  2. 判断一个抽象应该放在 webworkerpackages/shared 还是 ee
  3. 理解为什么 shared 不能 import 上层 package。

3.4.1 当前 package 依赖图

这张图只表示 TypeScript import 方向。它不表示运行时调用方向。

实际运行时里,webworker 不应该直接互相调用函数。它们通过两类边界连接:

  • 类型边界:packages/shared 里的 queue payload、domain schema、filter/query contract;
  • 状态边界:Redis/BullMQ、S3/blob、Postgres、ClickHouse。

3.4.2 编译时依赖和运行时通信不是一回事

以 ingestion 为例:

这里:

  • web import @langfuse/shared,用同一个 IngestionEvent schema;
  • worker 也 import @langfuse/shared,用同一个 payload type;
  • web 没有 import worker
  • worker 没有 import web
  • 真实数据通过 S3 和 Redis 流动。

这就是 Langfuse 的核心边界:共享契约,不共享运行时实现。

3.4.3 为什么 shared 不能依赖上层

packages/shared 位于依赖图底部。它不能 import webworkeree,原因不是形式主义,而是为了防止三类问题:

问题如果 shared 依赖上层会怎样
循环依赖web -> shared -> web 会让构建、测试和 tree-shaking 变复杂。
运行时污染worker 可能被迫加载 Next.js、React、session 逻辑或浏览器代码。
契约不稳定shared contract 会混入某个页面或 processor 的局部假设。

一个规则可以帮助判断:shared 里应该出现“跨边界稳定名词”,不应该出现“某个入口的局部流程”。

3.4.4 抽象放置决策表

改动对象应放位置原因
React component local stateweb/src/features/...只服务 UI。
UI 内部 API routerweb/src/server/api 或 feature server依赖 session、Next.js、tRPC context。
Public API route wrapperweb/src/features/public-api/server依赖 Next.js request/response 和 API key auth。
Public API request/response schemaweb/src/features/public-api/types + fern/apis/**外部 contract,需要文档和 SDK 对齐。
Queue payloadpackages/shared/src/server/queues.tsproducer 和 consumer 跨进程共享。
Redis queue classpackages/shared/src/server/redis/**producer 和 worker 都可能创建/解析队列。
Ingestion event/domain schemapackages/shared/src/server/ingestion/**domain/**request side 和 worker side 都要理解。
Worker processorworker/src/queues/**只在 worker 运行。
ClickHouse query builderpackages/shared/src/server/queries/**web/API/worker 可能共享 query 语义。
ClickHouse write batchingworker/src/services/ClickhouseWriter当前执行在 worker 内,处理批量写入策略。
Enterprise feature implementationee/**web/src/ee/**商业能力,但依赖基础 shared contract。

3.4.5 判断是否该上升为 shared

把一个 helper 放进 shared 之前,先问:

  1. 它是否会被 webworker 同时 import?
  2. 它是否表达 Redis job、S3 key、ClickHouse row、Public API response 这类跨边界数据?
  3. 它是否包含 tenant scope、权限、filter operator、field mapping 这类系统规则?
  4. 它是否需要在 rolling deploy 中保持兼容?
  5. 如果它变更,是否至少有两个运行时会受到影响?

如果这些问题大多是 “yes”,它更像 contract。否则它可能只是局部 helper。

3.4.6 依赖方向和测试策略的关系

依赖方向也决定了验证范围:

改动位置风险面验证倾向
web 局部 componentUI 行为和浏览器渲染targeted component/test + browser review。
worker processorjob retry、side effect、吞吐targeted worker test。
packages/shared queue/schema/queryweb 和 worker 同时受影响lint + 至少一个 web 侧和一个 worker 侧回归。
fern/apis外部 API contractAPI tests + Fern regeneration。
Prisma/ClickHouse schema数据迁移和读写同构db generate + migration/query/writer 回归。

shared 改动不是因为代码更底层就更安全,恰恰相反,它的 blast radius 更大。

3.4.7 常见误区

误区正确理解
web 能用的东西都放 shared只有跨运行时或跨 contract 的东西才放 shared。
worker 可以直接 import web serviceworker 应通过 shared service/contract 或自己的 service 实现。
queue payload 加字段只是类型改动队列里可能已有旧 job,consumer 要兼容。
ee 可以绕过 sharedEnterprise 也应建立在同一基础 contract 上。
手写 SQL 更快可能绕过 tenant filter、field set、query tag 和 ClickHouse 排序优化。

下一节

数据设施