12.7.2 别让升级搞坏旧应用——向后兼容:破坏性变更的避免
一句话破题
向后兼容意味着"新版本不破坏旧代码"——这需要在设计阶段就考虑未来的演进。
什么是破坏性变更
| 变更类型 | 示例 | 是否破坏性 |
|---|---|---|
| 添加可选字段 | 响应中新增 meta 字段 | 否 |
| 添加必填字段 | 请求必须包含 userId | 是 |
| 删除字段 | 移除 legacyId 字段 | 是 |
| 重命名字段 | user_name → userName | 是 |
| 改变类型 | id: number → id: string | 是 |
| 改变行为 | 排序顺序变化 | 可能 |
添加字段而非删除
typescript
// 旧版本响应
interface UserV1 {
id: number;
name: string;
}
// 新版本响应(向后兼容)
interface UserV2 {
id: number;
name: string;
email?: string; // 新增可选字段
displayName?: string; // 新增可选字段
}
// 客户端代码仍然有效
function displayUser(user: UserV1) {
console.log(user.name); // 仍然有效
}使用适配器模式
当必须改变结构时,提供适配层:
typescript
// 内部使用新结构
interface UserInternal {
id: string; // 改为 string
profile: {
displayName: string;
email: string;
};
}
// 对外提供适配的旧结构
function toV1Response(user: UserInternal): UserV1 {
return {
id: parseInt(user.id), // 转回 number
name: user.profile.displayName,
};
}
function toV2Response(user: UserInternal): UserV2 {
return {
id: user.id, // 使用新的 string 类型
displayName: user.profile.displayName,
email: user.profile.email,
};
}功能开关
使用功能开关逐步迁移:
typescript
interface FeatureFlags {
useNewUserFormat: boolean;
enableEmailField: boolean;
}
async function getUser(id: string, flags: FeatureFlags) {
const user = await db.user.findUnique({ where: { id } });
if (flags.useNewUserFormat) {
return {
id: user.id,
profile: {
displayName: user.name,
email: flags.enableEmailField ? user.email : undefined,
},
};
}
// 返回旧格式
return {
id: parseInt(user.id),
name: user.name,
};
}兼容性测试
typescript
import { describe, it, expect } from 'vitest';
describe('API 兼容性测试', () => {
it('v2 响应应该兼容 v1 客户端', async () => {
const v2Response = await fetch('/api/v2/users/1');
const data = await v2Response.json();
// v1 客户端期望的字段必须存在
expect(data).toHaveProperty('id');
expect(data).toHaveProperty('name');
expect(typeof data.id).toBe('number'); // v1 期望 number
});
it('新增字段不应破坏旧客户端', async () => {
const response = await fetch('/api/v2/users/1');
const data = await response.json();
// 旧客户端忽略新字段,不应报错
const oldClientCode = () => {
console.log(data.name);
};
expect(oldClientCode).not.toThrow();
});
});AI 协作指南
- 核心意图:让 AI 帮你评估变更是否破坏兼容性。
- 需求定义公式:
"请分析这个 API 变更是否是破坏性的,如果是,请建议如何保持向后兼容。" - 关键术语:
向后兼容 (backward compatibility)、破坏性变更 (breaking change)、适配器模式
避坑指南
- 添加而非删除:永远不要删除字段,只添加新字段。
- 可选而非必填:新字段尽量设为可选。
- 文档标注:明确标注哪些字段可能在未来版本变化。
