Skip to content

TypeScript 单仓图治理:Project Graph、受影响构建与发布切片

8 min read

先统一目标:Monorepo 图治理 要解决什么

单仓治理的核心不是“把代码放一起”,而是“让依赖关系可计算、可约束、可审计”。如果团队只靠目录约定,仓库规模一大就会出现循环依赖、跨层调用、无差别全量构建。Project graph 提供了一个更工程化的视角:每次变更先映射到图,再从图推导构建、测试、发布范围。这样 CI 才能既快又稳。

把治理规则写进图:不是约定,而是自动阻断

建议给每个包打上能力标签(domain、application、infra、ui 等),并维护允许依赖矩阵。例如 domain 不得依赖 ui,application 不得直接依赖 infra 实现。然后在 lint 与 graph 检查中强制执行。受影响构建算法要包含两类边:静态导入边和生成产物边,否则会漏掉 schema/代码生成带来的隐式影响。发布切片时,优先按图中的连通子图分批,先发布低风险叶子包,再发布核心包。这样即使出问题,也能快速定位并回滚局部。

Project Graph 与受影响任务流

flowchart LR
A[代码变更] --> B[解析依赖图]
B --> C[计算受影响子图]
C --> D[构建受影响包]
C --> E[执行受影响测试]
C --> F[生成发布切片]
D --> G[缓存写入]
E --> G
F --> H{门禁通过?}
H -- 否 --> I[阻断并输出依赖报告]
H -- 是 --> J[分批发布]

上图体现的核心原则是:契约与类型不是文档附属物,而是发布系统的硬门槛。每一个节点都要能产出可审计证据,例如差异报告、失败样本、影响范围清单。

关键实现片段:边界规则声明(示意)

type Layer = 'domain' | 'application' | 'infra' | 'ui';

const allowed: Record<Layer, Layer[]> = {
  domain: ['domain'],
  application: ['domain', 'application'],
  infra: ['domain', 'application', 'infra'],
  ui: ['application', 'ui']
};

export function canDependOn(from: Layer, to: Layer): boolean {
  return allowed[from].includes(to);
}

代码片段强调一个现实问题:只有把类型推导与运行时校验绑定在同一模型上,团队才能避免“类型看起来没问题,线上却持续报错”的反复返工。

接口语义栅栏:杜绝同名异义

在 Monorepo 图治理 场景中,类型系统的第一职责不是“炫技”,而是把团队共享语义固化为可审计资产。建议把类型分成三层:基础层(原子值与工具类型)、领域层(业务语义对象)、边界层(输入输出契约)。每一层都要定义 owner、发布节奏和兼容策略。特别是领域层,禁止把数据库字段命名直接暴露给上层业务,否则一旦存储结构变化,类型将成为反向耦合点。实践中可以通过“类型评审清单”控制质量:是否存在同义重复类型、是否出现 any 漏洞、是否有不可解释的条件类型嵌套、是否提供了最小可复现实例。

规模化阶段还需要治理类型债务。建议每周输出类型债务看板,指标至少包含:any 占比、类型断言占比、公共类型破坏兼容次数、类型检查耗时。看板不是为了追责,而是为了排优先级。对于高频变化模块,可采用“稳定接口 + 实验接口”双轨策略:稳定接口承诺兼容,实验接口允许快速试错并设置过期时间。这样可以在保证主干稳定的同时保留创新速度。

跨服务契约钩子:版本漂移即时报警

任何外部数据都是不可信输入。Monorepo 图治理 的高可用实践要求你在入口、出口、事件总线三个边界统一执行 schema 校验。入口校验保证请求体可解析,出口校验保证对外响应不漂移,事件校验保证异步链路可追溯。建议把校验失败分成“可恢复错误”和“不可恢复错误”两类:前者返回可读错误并上报指标,后者直接阻断并触发告警。

很多线上问题来自“类型看似正确但值语义错误”,比如金额字符串被当成数字、时区字段格式不一致、枚举值历史兼容被破坏。应对方法是把关键字段引入语义校验器,例如金额必须是最小货币单位整数、时间必须是 ISO 8601、枚举必须附带版本映射。这样做的收益是错误更早暴露,排障半径更小。

依赖扇出控制:减少一次改动触发的重建范围

构建优化必须先找关键路径。建议在本地与 CI 同时采集三个指标:TypeScript 检查耗时、打包耗时、测试耗时,并按包维度输出 Top N。针对耗时热点可采用四步法:第一步拆分项目引用,减少单次检查图规模;第二步启用增量与缓存,降低重复计算;第三步剔除无效输入,比如把文档、快照等不相关文件排除在构建图外;第四步按受影响范围运行任务,避免全量重复。

不要把 skipLibCheck 当万能按钮。它能短期提速,但会掩盖依赖类型问题。更稳妥的方式是对高质量依赖保持校验,对低质量历史依赖做分层白名单并设清理期限。若团队采用 monorepo,应把构建缓存命中率纳入发布门禁,低于阈值时自动输出失效原因报告,持续治理输入不稳定问题。

发布切片策略:按图切批次,按风险设门禁

单仓治理强调“图优先”。目录结构只能表达归档方式,不能表达真实依赖。必须定期导出 project graph,检查循环依赖、跨层依赖、过高扇出节点。对高扇出包要谨慎改动,因为它们会放大构建成本和回归风险。建议为每个包声明责任域与依赖白名单,PR 阶段自动校验,不符合规则直接阻断。

发布策略建议采用“按图切片 + 按风险分级”。低风险叶子包可并行发布;核心基础包需要灰度和回归验证后再全量放开。每次发布后要回写依赖快照,确保问题复盘时能准确还原版本组合。治理的关键不在工具多少,而在规则是否可执行、可追踪、可复盘。

Mock 策略边界:只隔离不逃避真实契约

测试不是单一维度。Monorepo 图治理 至少需要三类验证:类型层验证 API 推导与兼容,运行层验证业务行为与异常分支,契约层验证边界输入输出。类型层建议对公共类型与关键泛型做快照或断言,避免“重构不报错但语义变了”。运行层应覆盖正常路径、失败路径、边界路径,并在错误分支中验证日志字段是否完整。

契约层测试要和发布流程绑定。每次 schema 或 OpenAPI 变更都应自动生成回归样本,验证新版本对旧调用方是否兼容。对于不可兼容变更,必须附带迁移说明与灰度计划。测试结果应直接驱动发布决策,而不是仅作为报告存在。只有当测试成为门禁而非建议,质量收益才会稳定出现。

落地执行手册:把策略变成日常动作

为了让 Monorepo 图治理 持续有效,建议按“周、迭代、季度”三层节奏运营。周节奏关注快反馈:处理新增类型债务、修复契约告警、检查构建预算偏移。迭代节奏关注中期目标:完成一批边界收敛、消化历史豁免、提升缓存命中率。季度节奏关注结构优化:重构高扇出模块、调整包边界、升级工具链版本。

组织协作上可采用 RACI 模型:平台组负责规则与基础设施,业务组负责具体改造与回归验证,测试组负责门禁质量与覆盖率审计。所有改动都应沉淀到可追溯产物:ADR、变更报告、回滚记录、事故复盘。建议把“未闭环事项”挂入下一迭代看板,避免治理动作半途而废。

风险控制建议使用四象限:高影响高概率问题优先自动化治理;高影响低概率问题通过演练与应急预案兜底;低影响高概率问题交给 lint/脚本自动修复;低影响低概率问题定期清理。只要每个问题都有明确归属和关闭条件,治理就不会停留在口号层面。

故障演练与回滚剧本

Monorepo 图治理 的发布前应至少准备三种演练:契约不兼容演练、构建性能退化演练、错误传播失控演练。契约演练模拟字段删除或语义变化,验证门禁能否准确阻断;性能演练模拟依赖扇出激增,观察是否触发预算告警;错误演练模拟下游超时与数据污染,验证降级路径和告警路径是否可用。

回滚剧本要写到可执行层面:谁有权限回滚、回滚到哪个版本、如何验证恢复、如何通知相关方。推荐把回滚验证自动化,至少覆盖核心健康检查、关键接口 smoke test、错误率指标恢复。回滚后必须做复盘,复盘关注“为何没在更早阶段发现”,而不是停留在“谁操作失误”。

如果团队规模较大,可建立“变更分级发布”机制:A级变更需要双人审批与灰度窗口,B级变更可自动发布但保留快速回滚,C级变更进入标准流水线。分级的目标是让流程与风险匹配,而不是增加审批负担。