组织拆分后接口只改了一半,另一侧悄悄坏掉
某后台系统在业务初期把两个审批角色合并管理,接口路径里直接刻入了「合组时代」的命名。半年后两个团队正式拆分,功能需求开始各走各路——但工程师只顺手改了提需求那一侧,另一侧的同名接口被默默遗忘,像一颗延迟引爆的地雷。
等用户报告「我怎么看不到自己这边待审的单?」,排查半天才意识到:那个接口压根就没有对称副本。
现象
# 2023 年两组合并时建的接口
GET /api/group-a-b-leader/pending-reviews
POST /api/group-a-b-leader/batch-approve
# 2024 年拆分,只有 A 组提了需求
# → A 组的过滤逻辑被更新了 8 次,B 组调同一路径,拿到的数据逻辑已经偏
Git log 上看得很清楚:这个接口 A 组相关的 commit 有 8 条,B 组相关的 0 条。前端已经拆成了两个独立页面,但两个页面调用的是同一个路径。RBAC 规则里写的还是合组时代的 group-a-b-leader,B 组的角色根本没有对应权限配置。
根因
接口路径是对组织架构的一次快照。路径里编码了「建接口那一天」的 org chart。当组织变化时,有三种形态:
- 角色重命名:风险低,改路径 + 更新 RBAC 即可
- 角色合并(A + B → AB):风险低,其中一侧变成休眠状态
- 角色拆分(AB → A + B):风险最高,也最容易漏
拆分之所以危险,是因为它的「安全幻觉」:旧接口继续对两侧都能返回数据,短期内看起来没坏。直到两侧需求开始分叉,第一个改动必然只改一侧,另一侧就悄悄偏了。PM 按团队维度跟需求,没有人持有一张「合组时代接口全量清单」,所以谁都没注意到这件事。
拆分后的接口如果没有做显式的对称性决策,每一次功能迭代都是在「默认只改一侧」。累积几轮之后,两侧用户看到的数据语义已经完全不同,但日志里永远显示 200 OK——这是最难发现的静默漂移。
正解
把组织拆分时刻当成一次结构化审计事件,而不是背景迁移。
第一步:枚举所有合组时代接口
grep -rn "group-a-b\|groupAB\|GroupAB" src/ docs/清单覆盖:路由路径、Controller/Service 命名、RBAC 规则、前端路由、数据库视图。
第二步:对每个接口做三选一决策
| 决策 | 适用场景 | 行动 |
|------|---------|------|
| 拆分 | 两侧逻辑或过滤条件会分叉 | 建 2 个独立接口,旧路径返回 410,更新所有调用方 |
| 参数化 | 逻辑一致,仅需 ?category=a 区分 | 增加必填 query 参数并校验,保留单接口 |
| 废弃 | 已无用或纯只读报表 | 标记 deprecated,排期删除 |
决策必须写进拆分 ticket,不允许「暂时先不管」作为默认选项。
第三步:为「没人提需求的那侧」单独建 issue
ISSUE-NNN: [B 组长] 补齐 /api/group-b-leader/* 与 /api/group-a-leader/* 的对称接口
Status: backlog(暂无用户需求,但对称性必须保证)
Blocks: A 侧任何后续功能迭代
这一步是关键。「等 B 组提需求再说」在实践中等价于「永远不做」。
第四步:加路由边界测试
// tests/routes-boundary.test.ts
it('禁止在新路由中重新引入合组时代复合命名', () => {
const routes = enumerateAllRoutes()
const forbidden = routes.filter(r => /group-a-b|dept-x-y/.test(r))
expect(forbidden).toEqual([]) // 白名单:待迁移的存量路由
})防止下一轮需求又在旧命名上叠加。
在 ticket 里逐接口记录「拆 / 参数化 / 废弃」三选一决策,并为「另一侧」提前建 backlog issue——不管他们现在有没有需求。这是阻止接口不对称债务悄悄累积的唯一可靠手段。
一句话外卖
接口路径是组织架构的快照,org chart 一旦重组,旧快照就是技术债计时器——拆分时不做显式清点,每次单侧迭代都在给另一侧埋雷。