API验证和合同互操作性
TL;DR
兼容性是纪律,不是运气。保持明确的版本策略(SemVer),更改数学("破裂",没有),合同测试,电路寄存器和Sunset过程。对于金钱和合规性-严格的带有vN的REST/gRPC,对于UI聚合-带有"@deprecated"的进化GraphQL。总是:金丝雀流量,向后兼容性≥一个发布周期,迁移海达,场使用遥测。
1)基本概念和目标
Backwards compatible (BC):较旧的客户端适合新服务器。
Forwards compatible (FC):新客户机适合旧服务器(受限制)。
电线兼容性:"电线"上的格式不会断裂(对于gRPC/Protobuf尤其重要)。
SemVer: `MAJOR.MINOR.PATCH'-打破合同→晋升MAJOR。
目标:尽量减少破坏性变化,并提供可预测的迁移窗口。
2)更改矩阵: 什么是可能的,什么是不可能的
3)不同的API样式的策略
3.1 REST
URI("/v1/……")或域中的版本("api-v1.")。头版仅适用于内部情况。
添加,不要删除:新字段-ok,旧字段-在图中标记为"deprecated"并至少保留一个周期。
状态/错误: 不要更改代码和结构。code/error.message/error.details`.
相似性不变:不要将安全的"POST"与"Idempotency-Key"转换为"行为上不同"的挑战。
3.2 gRPC / Protobuf
字段编号是神圣的:不要过度使用删除的数字,标记为"保留"。
仅添加新的选项/修复字段;"严格强制"-通过验证,不是"要求"。
测试包: 'payments。v1`, `payments.v2`.
服务互操作性:新的RPC →新方法;我们不会改变老人的行为。
proto message Payout {
reserved 4 ;//field deleted, number reserved string id = 1;
string currency = 2;
int64 amount_minor = 3;
// v2: optional string comment = 5;
}
3.3 GraphQL
没有v2的演变:添加字段/类型;删除-通过'@deprecated (reason)'并发布窗口。
Persisted Queries:对于公共客户,使用白色查询列表-更容易控制兼容性。
Field-level authZ和遥测:在删除之前,要知道实际使用哪些字段。
graphql type Payout {
id: ID!
amountMinor: Long!
currency: String!
comment: String @deprecated(reason: "Use note")
note: String
}
3.4 Webhooks
路径上的版本('/webhooks/v1/payments')和固定的事件信封('event_id','type','ts','data')。
签名/NMAS保持不变;新算法-作为带有标志的选项。
扩展-仅通过新的"data."和"headers"字段-而不删除旧字段。
4)网关API和版本路由
基于规则的路由:在前缀"/v1"上,在标题"X-Aapi-Version"上,在域上。
影子/金丝雀:在新版本的"阴影"中反映部分生产流量,比较答桉。
Rate/Quotas每版本:在迁移过程中保护旧客户。
- "日落:
-版本关闭日期 - "Deprecation: true"-版本过时
- `Link:
;rel="deprecation"'-在changelog/迁移海德
nginx location ~ ^/v2/ {
proxy_pass http://api_v2;
}
location ~ ^/v1/ {
add_header Deprecation "true";
add_header Sunset "Thu, 01 May 2026 00:00:00 GMT";
proxy_pass http://api_v1;
}
5)计划注册和合同
OpenAPI / JSON Schema для REST;Protobuf descriptors для gRPC;SDL registry для GraphQL.
CI检查:公关时,linters+"断断续续检查"。
消费者驱动合同(CDC):消费者测试(Pact/对应)-防止不可见断裂。
Changelog:机器可读性(例如'CHANGELOG。注册表中的md'+发行说明)。
6)领域的演变: 实用规则
ID/密钥:没有新的字段"_v2"和过渡期,请勿更改格式(UUID↔int)。
时间/货币:保持UTC ISO-8601/epoch和amount_minor+货币;不要改变规模(一美分/美分)。
Enum:添加值-ok;不要改变旧的意思。对于REST-给出字符串值而不是ints。
分离:基于cursor的更稳定;不要改变光标的语义。
7)剥夺和"日落"-procedura
1.公告(T-90/60):changelog,发送给合作伙伴,标题"Deprecation/Sunset"。
2.重复期:V1和V2并行运行;V1在响应/逻辑中提供了警告。
3.使用遥测:还有谁叫V1?点触点。
4.V1冷冻:只有bagfixs/无幻影。
5.关闭(Sunset): 410 Gone或带有迁移说明的块页。
8)无痛发布: 布局策略
Blue/Green或Weighted routing: 1-5-25-50-100%的流量。
兼容性窗口:最少1-2次要版本,更常见的是外部API 6-12个月。
Feature Flags:在不更改版本的情况下启用新的逻辑字段/分支。
阅读/写入拆分:首先添加对读取新字段的支持,然后开始编写。
9)兼容性测试(实践包)
对旧版本的响应进行金测试。
双重电路测试:禁止在CI中打破。
在V2(影子)的舞台上重播生产轨道。
Back/Forward脚本:旧服务器上的新客户端,反之亦然(如果允许FC)。
Webhook合同测试:验证签名,格式,时间。
10)验证过程的度量和SLO
上次MINOR的客户百分比(目标≥ Sunset之前的80%)。
版本兼容性/不可用错误(目标-0)。
旧版的呼叫比例(降至Sunset)。
客户端迁移时间(中位数/p95)。
版本之间的Latency/regression delta(不比基本版本差)。
11)工件示例
OpenAPI(片段,字段剥离):yaml components:
schemas:
Payout:
type: object properties:
id: { type: string, format: uuid }
amount_minor: { type: integer }
currency: { type: string }
comment:
type: string deprecated: true description: "Use note"
note: { type: string }
Protobuf(保留和v2包):
proto syntax = "proto3";
package payouts. v1;
message Payout { reserved 5; string id=1; int64 amount_minor=2; string currency=3; }
GraphQL(剥离):
graphql type Query { payout(id: ID!): Payout }
12)转化相邻通道
SDK/CLI:SemVer+依赖API版本,README中规定了兼容性。
事件/流(WS/Kafka):envelope版本。version`;新属性-可选;两种版本之间的工作方式相同。
报告/CSV:文件/帽名版本;在右侧添加列;不要改变顺序/类型。
13)政府和角色
合同所有者(域所有者)、API Steward(规则和林特)、Release Manager(日落/通信)。
用于中断更改的RFC过程:业务桉例,迁移计划,工件,日期。
单个API目录:其中可见架构、版本、Sunset日历、联系人。
14)反模式
"安静"断裂:我们更改状态/错误/字段类型,没有版本。
重新使用protobuf号码-破坏后继和旧客户端。
GraphQL无需遥测字段使用-删除"摸索"。
全局v2是大型游戏而不是点演化。
公共API查询参数中的版本是一个非突出且易受攻击的方案。
没有移民盖德和例子-合作伙伴拖累,时间表中断。
15)查看新版本发布列表
- 更新了电路(OpenAPI/Protobuf/SDL),通过了linter和breaking-checks。
- 添加集成和合同测试(CDC)。
- 准备好SDK/示例代码/迁移海德和 Changelog。
- 包含"Deprecation/Sunset"(旧版本)+"How to migrate"页面。
- 金丝雀/影子计划,Alerts和dashbords比较指标。
- 向后兼容性保持一致的时间。
- 回滚计划(rollback)和风险矩阵是一致的。
总结
稳定API是一个过程,而不是"一劳永逸"。遵守规则:SemVer+仅添加进化+电路寄存器+合同测试+日落程序。共享样式(REST/gRPC/GraphQL)及其策略,将版本路由到Gateway API,推出金丝雀,测量效果。因此,您将避免出现"惊喜",加快合作伙伴集成,并保持现金和合规关键域的可预测性。