本章内容包括:
- 运行破坏性变更检查
- 使用 API 版本管理方案
- 制定破坏性变更政策
在 API 产品的设计阶段,API 团队会不断迭代设计并更新 OpenAPI 定义文件。了解团队引入的任何变更是否向后兼容非常重要。本章将演示如何使用 API 差异(diff)工具来理解 API 定义文件的变更,并检测哪些是破坏性变更。避免给现有 API 使用者带来破坏性变更的一种方法是 API 版本管理,我将介绍不同组织采用的各种 API 版本管理方案和适用范围。同时,我会讲解引入新 API 版本的成本,以及如何通过破坏性变更政策文档向使用者传达你的 API 变更管理策略。最后,我会提供一些在工作流中管理破坏性变更的实用建议。本章内容对 API 开发者、产品经理和产品负责人都非常重要。
如果你按照附录 B 第 B.8 节的指引完成了 API 工具的安装,那么你已经拥有本章练习所需的 Tufin/oasdiff 工具。要进行本章练习,请进入本书代码仓库(github.com/apiopsbook/... chapter4 目录。
4.1 APIOps 工作流中的 API 定义变更
在 API 产品生命周期的发布阶段,当你发布新的 API 定义文件版本时,API 使用者通常会想知道新旧版本之间发生了哪些变化。但实际上,即便是在设计阶段被要求审查 API 定义文件的变更时,通常你也会先想从结构层面了解有哪些变更(不仅仅是简单的文本差异)。比如,是不是新增了一个路径?新增了响应属性?还是删除了某个端点?如果变更较小或是全新定义文件(也就是说完全是新内容),这些通常很容易发现。但如果是对一个大型、已有的 API 定义文件做了多处修改,辨别变更就不那么容易了。最后,判断这些变更是否向后兼容也非常关键,即确认现有集成该 API 的使用者在新版本发布后能否继续正常工作。图 4.1 展示了在你的 APIOps 工作流中,在哪些环节理解"发生了什么变更"是特别有用的。

4.2 理解变更内容的问题
假设你被要求审查一份 API 定义文件的变更。比如旧版本的 API 定义文件是 api.yaml,新版本是 apiv2.yaml。在新版本中,删除了一个端点 GET /v1/catalog/categories(包括所有仅与其关联的引用对象)。此外,端点 GET /v1/catalog/products 的描述和请求头也被更新,还新增了两个请求头值------x-rate-limit 和 x-rate-limit-remaining。图 4.2 对此进行了说明。
但假如你并不知道具体发生了哪些变更。虽然你可以对两个文件做简单的文本差异比较,查看哪些行发生了变化,但如何快速判断这些变更具体关联到哪些 API 操作呢?你可以渲染 API 定义文件,查看变更行所在的操作,但这需要一定的分析和判断。有没有一种简单的方法,可以快速获得变更的高层次概览呢?理想情况下,这个高层次概览应展示 OpenAPI 文档结构的变化情况。

在你着手解决这个问题之前,先退一步,回顾一下 OpenAPI 定义文件的结构。OpenAPI 定义文档可以采用 JSON 或 YAML 格式,文档中的根对象是 OpenAPI 对象。以 OpenAPI 3.0 版本为例,它包含多个字段,如图 4.3 所示。

OpenAPI 的 paths 对象可以包含多个 path item 对象,每个 path item 对象又可以包含多个子级的 operation 对象,如图 4.4 所示。

将 API 定义文件的变更表示为 OpenAPI 文档结构的变更,可以为你提供所需的高层次视图。使用以 OpenAPI 文档结构呈现变更的 OpenAPI 差异(diff)工具,可以帮助你实现这一点。
4.3 API 差异(diff)
OpenAPI 差异工具接受 OpenAPI 定义文件的变更前和变更后版本作为输入,输出两个版本之间发生变更的 OpenAPI 对象信息。在本场景中,对 api.yaml 和 apiv2.yaml 这两个版本的 API 定义文件进行差异比较,应该能显示出一个端点被删除,另一个端点被更新,如图 4.5 所示。

有一种差异工具是 Tufin/oasdiff(github.com/Tufin/oasdi... API 差异工具,其他还包括 openapi-diff(github.com/OpenAPITool... optic(github.com/opticdev/op...
提示:还有一些有趣的商业 API 工具专门设计用于检测破坏性变更。Bump.sh(bump.sh)提供了一个-s18dohkus4dn46e/) API 定义变更管理解决方案,其中包括破坏性变更检测,并具备一个非常整洁的自动生成 API 变更日志。与许多我见过的工具不同,Relio(www.getrelio.com)通过对应用代码进行静态分析来检测破坏性变更,然后将结果与,-yi1ht3ct1m3peqpap90akro9ey97aqqfpra483g9yblxarhw0ub52ajtuhuv0iqchb290ito5ctl4bkra04di55i/) OpenAPI 定义进行比对,确保 API 文档的全面性和准确性。
OpenAPI 差异工具允许你以不同格式报告差异(例如 YAML、JSON、Markdown 或 HTML)。作为命令行工具,它们非常适合在持续集成/持续交付(CI/CD)流水线中运行。Tufin/oasdiff 的默认差异输出格式是 YAML。我选择介绍 Tufin/oasdiff,是因为它的默认 YAML 差异输出遵循 OpenAPI 规范(OAS)的分层对象模型。注意,撰写时它支持 OpenAPI 3.0,计划支持 3.1 版本。
在你的章节项目中,可以运行以下命令来查看两个版本 API 定义文件之间的差异:
ruby
$ oasdiff diff api.yaml apiv2.yaml
输出示例如下:
yaml
paths:
deleted: #1
- /v1/catalog/categories
modified:
/v1/catalog/products:
operations:
modified:
GET:
description: #2
from: List all products.
to: List all products in Acme's catalog.
responses:
modified:
"200":
headers: #3
added:
- x-rate-limit-hour
- x-rate-limit-remaining
...
components:
schemas:
deleted: #1
- CategoriesResponse
说明:
- 路径
/v1/catalog/categories
被删除(包括仅被其引用的CategoriesResponse
组件)。 GET /v1/catalog/products
的描述被更新为"in Acme's catalog"。- 在
GET /v1/catalog/products
的 200 响应中新增了两个请求头:x-rate-limit-hour
和x-rate-limit-remaining
。
差异报告的顶层有一个 paths
节点,其值是一个映射,可能包含三个键:added
、deleted
或 modified
。每个键对应的值列出所有相应类别下发生变化的路径。当路径属于 modified
类别时,会进一步列出被修改的操作对象及其具体修改内容,同样使用 added
、deleted
或 modified
分类。
Tufin/oasdiff 还提供了另一种更简单的差异呈现模型。该模型按端点(endpoint)分组变更,而不是按路径。端点是路径与操作的组合,例如 GET /v1/catalog/products
。此模型的顶层节点是 endpoints
,其值也有三个可能的键:added
、deleted
、modified
,值为各类别下的端点列表。modified
类别下同样会列出被修改的操作对象及具体修改内容,仍使用 added
、deleted
、modified
分类。示例如下:
yaml
...
endpoints:
deleted: #1
- method: GET
path: /v1/catalog/categories
modified: #2
? method: GET
path: /v1/catalog/products
: description: #2
from: List all products.
to: List all products in Acme's catalog.
responses:
modified:
"200":
headers: #3
added:
- x-rate-limit-hour
- x-rate-limit-remaining
components:
schemas:
deleted:
- CategoriesResponse
说明:
GET /v1/catalog/categories
端点被删除(包括其引用的CategoriesResponse
组件)。GET /v1/catalog/products
的描述更新为"in Acme's catalog"。- 在
GET /v1/catalog/products
的 200 响应中新增了两个请求头。
路径模型与端点模型的唯一区别在于:路径模型中,变更由一个 YAML 标量(路径字符串)作为键表示;而端点模型中,变更由一个 YAML 映射对象(路径和 HTTP 方法)作为键表示。
YAML 的条目(entry)能否有两个元素作为键?
YAML 的本地数据结构是节点(node),主要有三种节点类型:
- 标量节点(scalar nodes),例如字符串或数字
- 序列节点(sequence nodes),即有序节点集合
- 映射节点(mapping nodes),即无序的键值对集合
如图所示:
序列节点以连字符(-)开头,映射节点的键和值之间以冒号(:)分隔。

YAML 节点模型的统一建模语言(UML)图示
你遇到的大多数 YAML 文件,映射节点(mapping node)的键通常只用标量节点(scalar nodes)。下面是一个 YAML 映射节点的例子,键是标量节点 deleted
,值是序列节点(sequence node):
yaml
deleted:
- method: GET
path: /v1/catalog/categories
但是,你也可以用序列节点或另一个映射节点作为映射节点的键,这被称为复杂映射键(complex mapping key)。下面是一个以映射节点作为键的 YAML 映射节点示例,键由 ?
表示,值是另一个映射节点:
sql
? method: GET
path: /v1/catalog/products
: description:
from: List all products.
to: List all products in Acme's catalog.
Tufin/oasdiff 使用复杂映射键来实现端点差异(endpoint diff)模型,示例:
yaml
endpoints:
deleted:
- method: GET
path: /v1/catalog/categories
modified:
? method: GET #1
path: /v1/catalog/products
: description: #2
from: List all products.
to: List all products in Acme's catalog.
responses:
modified:
"200":
headers:
added:
- x-rate-limit-remaining
- x-rate-limit-hour
说明:
- #1 复杂映射键以
?
开头。 - #2 复杂映射键的值以
:
开头。
想了解更多 YAML 规范内容,请访问 yaml.org/spec 。
使用 oasdiff 的完整差异报告后,你就知道审查 apiv2.yaml
时应关注哪些内容。如果只想要变更的摘要,而非完整差异,可以运行:
css
oasdiff summary api.yaml apiv2.yaml
输出示例:
yaml
diff: true
details:
endpoints:
deleted: 1
modified: 1
paths:
deleted: 1
modified: 1
schemas:
deleted: 1
4.4 检测破坏性变更
破坏性变更是指需要 API 消费者修改其代码,才能确保应用继续正常工作的变更。换句话说,就是非向后兼容的 API 变更,会导致集成了该 API 的客户端应用停止工作或服务质量下降。
举例来说,删除 API 路径(如前面场景中的 /v1/catalog/categories
)会导致现有消费者调用该端点时收到 404 Not Found 错误。相对地,非破坏性或向后兼容的变更不会干扰 API 消费者,他们可以在方便时逐步扩展自己的代码以适应新变更。
假设你想检查章节项目中 apiv2.yaml
的变更是否包含破坏性变更。使用 Tufin/oasdiff,可通过命令:
oasdiff breaking api.yaml apiv2.yaml
获得如下输出:
lua
1 breaking changes: 1 error, 0 warning
error [api-path-removed-without-deprecation] at
↪ original_source=api.yaml
in API GET /v1/catalog/categories
api path removed without deprecation
这表明删除 /v1/catalog/categories
路径是破坏性变更。但输出格式不限于文本,也可以用 JSON 格式显示,命令为:
css
oasdiff breaking api.yaml apiv2.yaml --format json
示例输出:
css
[ { "id": "api-path-removed-without-deprecation", "text": "api path removed without deprecation", "level": 3, "operation": "GET", "operationId": "listCategories", "path": "/v1/catalog/categories", "source": "api.yaml", "section":"paths" }]
太棒了!现在你可以自动检测 API 定义文件的变更是否为破坏性变更。
关于"废弃(Deprecation)"与"下线(Sunsetting)"
废弃是通知 API 消费者某个 API 或端点将来会被移除;而下线则是在废弃期结束后,让该 API 彻底不可用。后续章节会详细讲解废弃和下线。
假设你有一个已废弃的 API 端点,在某个 API 定义文件中标记了废弃,现在到了下线阶段。比如,chapter4
目录下:
apiv1-dep.yaml
:带有已废弃的GET /v1/catalog/categories
端点。apiv2.yaml
:已将该端点下线(即删除)。
下线端点是一种特殊的破坏性变更,因为你已经提前通知用户了,所以可能不希望自动检测把它当作失败。如何实现忽略已下线端点的破坏性变更检测呢?
Tufin/oasdiff 支持使用 OpenAPI 扩展 x-sunset
。在检测破坏性变更时,oasdiff 会忽略所有带有已过期 x-sunset
日期的端点。
OpenAPI 规范扩展(Extensions)
OAS 扩展允许通过自定义数据字段,支持特定用例。你可以用这些字段为 API 定义文件添加文档信息或让第三方工具识别和操作。
扩展字段名以 x-
开头,例如 x-sunset
用于指定 API 的下线日期。注意,字段名以 x-oai
和 x-oas-
开头的是规范保留字段。
示例:检查废弃端点的破坏性变更
在 apiv1-dep.yaml
文件中,废弃的 GET /v1/catalog/categories
端点有如下定义片段:
yaml
paths:
/v1/catalog/categories:
get:
deprecated: true
x-sunset: "2022-08-30"
tags:
- Categories
summary: List all categories
运行破坏性变更检测命令:
oasdiff breaking apiv1-dep.yaml apiv2.yaml
不会输出破坏性变更警告,因为 x-sunset
日期是过去的。若删除或注释掉 x-sunset
行,再次运行检测,则会得到破坏性变更提示。
小贴士
想了解 Tufin/oasdiff 认为哪些变更是破坏性的,可参考:mng.bz/aEAB 。
路径前缀修改
Tufin/oasdiff 还有个有趣功能,允许你在比较前修改 API 定义中端点的路径前缀。例如,从 api.yaml
版本升级到 apiv3.yaml
版本时,所有 URI 路径的前缀从 /v1/
改为 /v2/
。如果你想忽略这个路径前缀的变化,只关心其他破坏性变更,可以用命令:
bash
oasdiff diff api.yaml apiv3.yaml --strip-prefix-base /v1/ --prefix-base /v2/
--strip-prefix-base
会移除旧版本路径中的 /v1/
前缀,--prefix-base
则给它们加上 /v2/
前缀。此操作仅在内存中生效,不会修改文件。类似地,你也可以对新版本用 --strip-prefix-revision
和 --prefix-revision
。
4.5 生成 API 更新日志
API 更新日志是记录 API 随时间所有变更的历史文档。它详细列出了 API 中不同元素的新增、修改和删除,例如:
- 端点(Endpoints)------新增的端点列表,用于提供新功能;以及已废弃或删除的旧端点
- 参数(Parameters)------现有端点中参数的数据类型变化、变为可选/必选,或被删除
- 返回格式(Response formats)------API 返回数据结构的变化,比如新增字段、移除无用字段,或者消息格式整体改变
- 认证与授权(Authentication and authorization)------API 安全机制的变更说明
通过维护更新日志,开发者可以及时了解 API 的变更,预先调整代码以保持兼容性和功能正常。因此,更新日志也是开发者沟通的渠道,帮助他们预测变更,提前解决兼容性或稳定性问题。
不同 API 提供商的更新日志详情和格式有所不同,有的只列出简单变更,有的则包含更多信息,例如:
- 版本号和日期
- 新功能、增强和修复的描述
- 破坏性变更列表
- 废弃时间表
- 支持信息和相关文档链接
下面是一个 API 更新日志条目的示例:
bash
API 更新日志
版本 2.1.1
日期:2023-09-08
新功能和增强:
• 通过缓存机制优化 /search 端点性能 20%。
• 为 /products 端点新增分页支持,包含 limit 和 offset 参数。
破坏性变更:
• /users 端点移除 legacy_mode 参数,改用新的 version 头部。
• /products 响应中的 price 字段数据类型由字符串变为浮点数。
缺陷修复:
• 修复 /admin 端点某些用户角色认证失败的问题。
• 修正 /payments 端点对无效请求返回错误信息错误的问题。
废弃:
• /reports 端点中 old_format 标志已废弃,将在 3.0 版本中移除。
附加说明:
• 有关破坏性变更的迁移支持,请联系技术支持。
小贴士:更多 API 更新日志示例,可参见 Intercom(mng.bz/gv8E)和 Stripe(stripe.com/docs/change...
OpenAPI 差异工具能帮你计算两个不同版本 API 定义文件间的变化,从而生成更新日志条目。例如,使用 Tufin/oasdiff 生成 api.yaml
和 apiv2.yaml
之间变更摘要:
oasdiff changelog api.yaml apiv2.yaml
输出示例:
lua
2 changes: 1 error, 0 warning, 1 info
error [api-path-removed-without-deprecation] at api.yaml
in API GET /v1/catalog/categories
api path removed without deprecation
info [api-schema-removed]
in components/schemas
removed the schema 'CategoriesResponse'
其中,error
表示破坏性变更。你可以基于此输出(编辑格式、添加补充信息)来创建 API 更新日志条目,并发布到开发者门户。
我已经讲解了如何检测破坏性变更以及如何在更新日志中体现它们。接下来介绍破坏性变更与 API 演进的关系。
4.6 破坏性变更与 API 演进
前面提到,破坏性变更指非向后兼容的改动,迫使 API 消费者修改代码。对于 REST API,兼容性意味着客户端应用和服务器能正常通信。当服务器发布不兼容客户端的新变更时,客户端必须更新,否则无法继续通信,带来时间和成本负担。相反,非破坏性或向后兼容变更不会中断客户端,API 消费者可以自主安排扩展代码使用。
API 版本是一组保证与客户端兼容的 API 操作。该版本的变更预期是稳定且非破坏性的。引入破坏性变更通常意味着发布新版本 API。否则必须与所有 API 消费者协调,难度较大,尤其是面对广泛使用的 API。
那为什么提供商会引入破坏性变更?API 会随着新功能添加、现有功能增强和缺陷修复而演进,其中部分变更可能不兼容。API 一旦发布,消费者会依赖它的行为,所以演进必须谨慎,否则变更会导致客户端代码断裂。API 演进是新增功能提升体验与维护旧版本成本间的平衡。理想是边演进边降低维护旧版本的成本。
小贴士:对于刚起步的小型 API 提供商,向外部消费者明确 API 演进预期非常重要。建议在发布首个公共版本时,制定基本的破坏性变更政策,并通过自动化破坏性变更检测在 API 定义管道中强制执行(第 8 章会讲)。
4.7 API 版本控制方案
版本控制方案指的是 API 使用者如何指定所调用的服务提供商 API 版本。针对 REST API,有八种常见的版本控制方案。
4.7.1 基于路径的版本控制方案
此方案基于 URI,将版本标识符包含在 API 请求的 URI 路径中。常见格式为:https://{域名}/{api名称}/{版本标识}/{操作ID}。例如:
-
Slack 的 Status API 用于描述 Slack 平台的健康状况及事件、故障和维护报告,采用语义化版本号的路径参数:
bashcurl https://status.slack.com/api/v2.0.0/current # 查询当前活跃事件 curl https://status.slack.com/api/v2.0.0/history # 查询历史事件
-
Twilio 的短信服务(SMS API)使用日历版本号(Calendar Versioning,简称 CalVer,后面会讲)作为路径版本标识:
csscurl -X GET https://api.twilio.com/2010-04-01/Accounts/{TwilioAccountIdentifier}/Messages/{MessageIdentifier}.json -u {TwilioAccountIdentifier}:{TwilioAuthToken}
-
OpenAI 的 REST API 支持自然语言及代码生成任务,采用语义版本号中的主版本号(major version)作为路径版本号,格式为
/v{majorVersion}/
:bashcurl https://api.openai.com/v1/models -H 'Authorization: Bearer {api-key}' # 获取可用模型列表
基于路径的版本控制是最常见的方案,清晰地表达了请求的是哪个版本,且易于理解和实现,API 使用者也可以通过浏览器探索 API。但缺点是新版本需要新路径,导致服务提供商管理大量不同路径的 API,且从技术角度违反了 REST 规范中 URI 应唯一标识资源的原则,因为即使资源本身没变,URI 因版本变化也会变。
4.7.2 基于主机名(Hostname)的版本控制方案
也称为域名版本控制,类似路径版本控制,但版本号作为主机名的一部分出现,格式如:https://{版本号}.{域名}.com/路径。例如:
-
TikTok 在其 v1 API 中使用主机名版本控制:
perlPOST https://open-api.tiktok.com/user/info
-
v2 API 则使用不同主机名:
bashPOST https://open.tiktokapis.com/v2/user/info
主机名版本控制允许将请求路由到不同服务器,但客户端可能需要调整安全设置以允许访问新主机。
4.7.3 基于查询字符串的版本控制方案
版本信息作为查询字符串参数出现在请求中,格式一般为:
ruby
https://{域名}/{api名称}/{操作ID}?{版本参数名}={版本标识}
简单易懂,客户端使用方便。有些实现中如果未指定版本则默认最新版本,也有实现要求版本参数必填。
例如,Snyk REST API(mng.bz/eo8v)使用带有强制 CalVer 标识的查询参数版本控制:
sql
curl -X GET "https://api.snyk.io/rest/groups/?version=2023-03-08~beta" --header "Accept: application/vnd.api+json"
4.7.4 自定义请求头版本控制方案
用户在请求中指定专门的请求头,包含版本信息。这种方式让资源的 URI 保持不变,区别于路径版本控制。GitHub REST API 就使用了自定义版本头:
arduino
curl --header "X-GitHub-Api-Version:2022-11-28" https://api.github.com/zen
如果未指定版本头,GitHub API 默认使用 2022-11-28 版本。
4.7.5 Accept 请求头版本控制方案
又称内容协商版本控制或媒体类型版本控制。通过 Accept 请求头指定版本及媒体类型,例如:
bash
Accept: application/vnd.example+json;version=1.0
Accept: application/vnd.example.v1+json
GitHub 旧版 REST V3 API(现已退役)使用:
bash
Accept: application/vnd.github.v3+json
Tableau 新版 REST API 也采用此方案,详细内容见 4.8.2 节。
4.7.6 固定版本控制方案(Pinned Versioning)
又称时间点版本控制或动态日期版本控制。API 使用者第一次请求时,系统将当前 API 版本"固定"到其账户,之后该账户的所有请求都默认使用这个版本。用户准备升级时,可登录账户修改默认版本。
示例:
-
Stripe API 当前版本为 2022-11-15,首次请求:
bashcurl https://api.stripe.com/v1/charges -u {api-key}
之后所有请求默认使用该版本。用户可在 dashboard.stripe.com 登录更改默认版本。注意,Stripe 虽然路径中有
/v1/
,但不主动作为版本控制参数使用。 -
Plaid 也采用固定版本控制,例如:
rubycurl --request POST https://sandbox.plaid.com/auth/get --header 'Content-Type: application/json' --data '{"client_id": {Plaid-client-id}, "secret": {Plaid-API-secret}, "access_token": {access-token}}'
版本由账户信息 dashboard.plaid.com/team/api 定义。
-
Square 也是采用该方案的金融 API 服务商(mng.bz/ppo8)。
Stripe、Plaid 和 Square 会结合其他版本控制方案,允许用户在单次请求层面覆盖默认版本,具体见 4.7.8 节。
固定版本控制方案让 API 提供商可以在不更改端点的情况下演进 API,也能通过账户信息了解用户使用的版本,但实现起来较复杂。
4.7.7 无版本控制方案
在这种方案中,API 可能确实有版本,但 API 使用者无法在请求中指定想要使用的版本。例如,AWS S3 API 的版本是 2006-03-01,但使用者无法在请求中指定其他版本。若想从 S3 桶中获取一张图片资源,API 使用者可以这样请求:
css
curl https://{bucket-name}.s3.amazonaws.com/my-image.jpg --header "Authorization: {authorization-string}"
如你所见,API 使用者无法请求 API 的其他版本。另一个例子是 Slack Web API(api.slack.com/web)。它严格来说不... REST API,因为它通过 HTTP 提供远程过程调用(RPC)风格的操作,格式为:
sql
https://slack.com/api/{method-family}.{method-name}
但熟悉 REST API 的人也能很快上手。比如,用户可以通过以下请求搜索匹配某关键词的消息:
bash
curl https://slack.com/api/search.messages?query=test --header "Authorization: {authorization-string}"
那些不在请求中提供版本控制方案的 API 服务商,通常力求保证 API 稳定,不引入破坏兼容性的变更。但如果必须引入破坏性变更,则需要与所有使用者协调,这往往成本很高。
4.7.8 多种版本控制方案组合
有些 API 允许用户通过多种方式指定所需的 API 版本。前面提到过 Stripe API 使用固定版本(Pinned Versioning)方案,同时结合自定义请求头版本控制,允许用户在单次请求层面覆盖账户默认版本。例如:
bash
curl https://api.stripe.com/v1/charges -u {api-key} -H "Stripe-Version: 2022-11-15"
自定义请求头 Stripe-Version
允许用户指定日历版本号,覆盖账户默认的 API 版本设置。Plaid 采用类似方式,使用 Plaid-Version
请求头;Square 使用 Square-Version
请求头。
注意,Stripe 还在路径中保留了版本号参数 /v1/
,但实际并不主动使用它,因为这会对大量用户造成重大影响。
Foursquare REST API 则结合了查询参数版本控制和路径版本控制。它用必填的日历版本号(CalVer)作为查询参数 v
(CalVer 是基于日期的版本号格式,详见 4.9.2 节)。例如,要查询某地的寿司店列表,可以这样请求:
vbscript
curl --request GET \
"https://api.foursquare.com/v2/venues/search?client_id={client-id}&client_secret={client-secret}&ll={latitude,longitude}&query=sushi&v={YYYYMMDD}"
虽然该 API 路径中带有版本标识 /v2/
,但仅为兼容旧版保留,不太可能发生变动。
4.8 API 版本控制范围
我们已经了解了 API 版本控制方案,即 API 使用者如何请求特定版本的 API。另一个需要考虑的方面是提供者希望在什么层级或范围内演进 API。例如,提供者可能只想对 API 的一小部分做改动,比如仅修改某一个端点;也可能想演进某个被多个端点使用的资源;或者做出大规模改变,比如更换整个 API 的认证系统,影响所有操作。提供者演进 API 的不同粒度,称为版本控制范围。本节涵盖三种版本控制范围:全局版本控制、按资源版本控制和按端点版本控制。
4.8.1 全局版本控制范围
全局版本控制指的是所有 API 操作共享同一个版本。当新增版本时,所有 API 操作都必须升级以匹配该版本。Zoom API 就是一个例子,它使用基于路径的版本控制且范围是全局的。API v1 版本的操作基于如下路径:
创建 Zoom 用户:
ini
curl --request POST https://api.zoom.us/v1/user/create \
-d api_key={api-key} -d api_secret={api-secret} \
-d email={email-address} -d type={user-type}
列出用户的已排定会议:
ini
curl --request POST https://api.zoom.us/v1/meeting/list \
-d api_key={api-key} -d api_secret={api-secret} \
-d host_id={user_id}
获取 Zoom 网络研讨会信息:
ini
curl --request POST https://api.zoom.us/v1/webinar/get \
-d api_key={api-key} -d api_secret={api-secret} \
-d host_id={user_id} -d id={webinar-id}
升级到 API v2 后,Zoom 使用 OAuth 2.0 进行授权,所有操作路径均改为 api.zoom.us/v2,例如:
列出用户的已排定会议:
bash
curl --request GET https://api.zoom.us/v2/users/{userId}/meetings \
--header "Authorization: {access-token}"
创建 Zoom 用户:
json
curl --request POST https://api.zoom.us/v2/users \
--header "Authorization: {access-token}" \
-d '{ "action": "create", "user_info": {"email": "jchill@example.com", "type": 1} }'
获取网络研讨会信息:
css
curl --request GET https://api.zoom.us/v2/webinars/{webinar-id} \
--header "Authorization: {access-token}"
全局版本控制被广泛使用,尤其是在基于路径的版本控制方案中,易于理解且保证了 API 内所有操作的一致性。然而,纯全局版本控制在更新版本时对提供者和使用者来说代价较高,尤其当 API 端点数量庞大时,使用者必须从头重新集成所有内容。
有些采用全局版本控制的 API 提供者很少更新主版本,例如 Amazon S3 API 使用全局版本控制,且多年来版本非常稳定,目前版本为 2006-03-01。
4.8.2 按资源版本控制范围
此方案不是对整个 API 进行版本控制,而是对单个资源(或某些资源)进行版本控制。也就是说,提供者不会发布整个 API 的新版本,而是给特定资源引入新版本。按资源版本控制常用于 Accept 头和自定义请求头的版本控制方案。
一个例子是 Tableau API。Tableau 新的 RESTful 端点以 /api/-/
为基础路径,只有当资源发生破坏兼容性的变更时,才提供该资源的新版本。例如,列出某个站点的 Tableau 仪表板扩展设置,用户发起请求:
ruby
curl --request GET https://us-west-2b.online.tableau.com/api/-/settings/site/extensions/dashboard \
--header "Accept: application/vnd.tableau.extensions.dashboard.v1.SiteSettings+json" \
--header "X-Tableau-Auth: {authentication-token}"
响应中的 Content-Type
头会标明资源版本:
bash
Content-Type: application/vnd.tableau.extensions.dashboard.v1.SiteSettings+json
{
"extensions_enabled": true,
"allow_sandboxed": true,
...
}
如果请求中未指定 Accept 头,Tableau 会默认返回该资源的最新版本。
提示:想了解 Tableau 按资源版本控制的更多细节,请访问 mng.bz/OZXR 。
总体来说,按资源版本控制允许更细粒度地控制版本范围。引入新版本时,仅影响资源相关的代码范围较小。所有使用该资源的操作应支持新版本,但其他 API 使用者需要判断其他资源是否兼容新版本。
4.8.3 按端点版本控制范围
API 的端点(操作)拥有各自独立的发布和支持生命周期,不依赖于其他端点。它们有自己的 API 版本号,可能不适用于 API 中的其他端点。这也称为操作版本控制或方法版本控制。一个例子是 Snyk REST API,它使用基于查询字符串的版本控制方案,且版本控制范围是按端点的,例如:
sql
GET https://api.snyk.io/rest/groups/{group-id}?version=2023-01-30~beta
GET https://api.snyk.io/rest/groups/{group-id}/settings/iac?version=2021-12-09
这种方式的优点是能够实现细粒度的版本控制,使提供者可以在端点级别灵活演进和扩展 API,同时支持其他端点。因为每个端点有自己的生命周期,会经历独立的上线、弃用和下线阶段,与其他端点互不干扰。
请求版本与响应版本
一些 API 会在响应中向用户返回所提供的 API 或资源版本信息。Snyk REST API 使用自定义响应头 snyk-version-requested
来显示调用方请求的端点版本,用另一个头 snyk-version-served
显示服务器返回的资源版本。Shopify API 也使用响应头 X-Shopify-API-Version
来表示执行请求时所用的 API 版本,该版本可能与调用方请求中指定的版本不同。如果两者不一致,说明调用方仍在使用过时且不受支持的 API 版本,需要更新客户端代码以使用受支持版本。
按端点版本控制的一个挑战是为 API 使用者提供一份一致的 OpenAPI 定义文件,因为每个端点有独立的版本标识和生命周期,可能与不同版本的其他端点无法互操作。Snyk 通过允许用户从其开发者门户下载指定版本的 OpenAPI 定义文件来解决这个问题,例如:
ruby
https://api.snyk.io/rest/openapi/2023-03-03
https://api.snyk.io/rest/openapi/2023-02-15~beta
此外,在 API 定义文件中,每个 API 操作都有扩展规范 x-snyk-api-version
,显示该端点的 API 版本;x-snyk-api-releases
则列出该端点发布过的所有 API 版本。
4.9 API 版本标识符
针对不同的版本控制方案和范围,API 提供者还可以决定其版本标识符的格式。主要有三种选择:语义化版本(SemVer)、日历版本(CalVer)和稳定性版本。
4.9.1 语义化版本(SemVer)
语义化版本规范(SemVer)semver.org 推荐的格式为 MAJOR.MINOR.PATCH。其中,MAJOR 版本号表示破坏性变更,MINOR 版本号表示向后兼容的新功能,PATCH 版本号表示向后兼容的错误修复。
REST API 使用语义化版本时,通常只在请求中指定主版本号。例如基于路径的版本控制中,会用 /v1/
或 /v2/
表示主版本号。任何破坏兼容性的改动都必须更改主版本号,次版本号一般不指定。这保证了 API 的稳定性,新版本都发布在同一主版本号下。
一些公司会在 OpenAPI 定义文件的 $.info.version
字段里放完整的 SemVer。例如,截至撰写时,Pinterest v5 API 在其定义文件中 $.info.version
为 5.8.0
,但 URI 路径仅显示主版本号,如 https://api.pinterest.com/v5/user_account
。
提示:Pinterest OpenAPI 定义可见 mng.bz/Y7zB。
eBay 也采用类似模式,如其 Inventory API(mng.bz/GZBv),且响应中会... X-EBAY-C-VERSION
头,值为 SemVer。
4.9.2 日历版本(CalVer)
日历版本(CalVer)规范(calver.org)基于项目发布的日历时间。软件项目采用不同格式的-yt5f86x8oay8ym7nugtnfax96qkeb866xh5h.xn--ihqyv95jgxnt9k90szfccya962nr5ewnv/) CalVer,但 REST API 中常见的是用发布日期作为版本标识。Stripe API 使用 YYYY-MM-DD
格式(如 2022-11-15
),Shopify 管理 API 使用 YYYY-MM
格式。由于人们习惯记忆日期,这种方式更易记。
正如 4.7.6 节中"固定版本控制"所述,Stripe API 也采用 CalVer。
4.9.3 稳定性版本
这是一种特殊的版本名称,用以指示 API 的稳定等级。API 可指定诸如 stable(稳定)、unstable(不稳定)、beta(测试版)、alpha(开发版)等版本名来表达稳定性。
Shopify REST Admin API 使用基于路径的 CalVer 版本控制,例如:
bash
GET https://{shop}.myshopify/admin/api/2023-01/products.json
同时支持使用 unstable
版本标识,让 API 使用者可以预览正在设计和开发中的新功能,如:
ruby
GET https://{shop}.myshopify/admin/api/unstable/products.json
unstable
版本可能包含破坏性改动,且不保证会正式发布。
一些组织将稳定性版本与 SemVer 或 CalVer 组合使用。Snyk REST API 的版本标识格式为 {calver}~{stability-version}
,例如:
2023-03-03~experimental
表示实验版本,含有尚未发布的不稳定功能;2023-03-03~beta
表示测试候选版本;2023-03-03
表示正式发布版本,推荐用于生产环境。
从产品角度看 API 版本控制
在《API Evolution without Versioning》(mng.bz/z8eB)中,Bran... Byars 建议不应仅从架构角度看待版本控制。作为 API 产品负责人,还应将版本控制视为向用户暴露的产品接口的一部分。提供者有时为简化实现和清理旧代码而版本化,但这可能带来 API 使用者迁移成本高昂的问题。Byars 提出了评估版本控制方案的三个标准:
- 可行性(Viability) :版本控制方案是否解决了 API 使用者的问题?是否减轻了认知负担并隐藏了底层复杂性?
- 易用性(Usability) :版本控制方案是否让 API 使用者更容易使用?是否清晰、优雅,且承诺了稳定性?是否符合"最小惊讶原则",直观且易于学习和使用?
- 可实现性(Feasibility) :版本控制方案是否架构合理?是否使系统更易理解和管理?是否保护了下游系统?
此外,产品角度还应考虑以下因素(Marjukka Niinioja 在《The Right Way to Version Your API: The API Consumer's Perspective》中提出,见 www.youtube.com/watch?v=5M8...
- 业务和行业节奏:某些行业 API 使用者希望 API 快速演进,而另一些则因迁移成本高而抵制改动。比如汽车和医疗行业因监管严格及设备嵌入多,升级新版本代价高,API 演进较慢。
- 提供者与使用者间的关系和权力结构:若提供者更强势,能强制使用者升级;反之,强势的使用者可能左右演进节奏。高度监管的行业版本演进较慢。
- 破坏性变更判断:只有当功能被实际使用时,变更才算破坏性;未使用功能的变更不影响使用者。
- 工具支持:部分旧的 API 管理工具或文档门户可能不支持某些版本控制方案,需要确认 API 使用者所用工具兼容性。
- API 提供者的产品管理策略:版本控制策略通常随着对市场价值主张的深入理解而演进,可能调整 API 边界、版本方案和范围,以实现产品市场匹配。因此建议从 API 价值主张画布(见第5章第5.2.3节)入手,有助快速设计出满足使用者痛点和期望收益的版本控制方案。
4.10 版本迁移所涉及的成本
定义 API 版本控制方案以及想要演进 API 的范围非常重要。但你还必须考虑如何将这些信息传达给 API 使用者,以及在发布新版本时如何引导他们迁移到新的 API 版本。切换到新版本往往会涉及显著的成本。
当因破坏性变更而引入新 API 版本时,需要考虑多种成本。第一类是提供者的成本,包括构建、测试、部署和维护新 API 版本。新版本还必须告知用户,并提供迁移建议。如果 API 提供者为使用者提供了 SDK,那么这些 SDK 也必须更新、重新发布并完善文档。此外,旧版本 API 仍需支持。支持旧版本的成本是一种机会成本,因为这部分资源本可以用来开发新功能和提升新版本的开发体验。这也是为什么提供者倾向于减少支持的旧版本数量,并鼓励使用者迁移到新版本。
第二类成本与 API 使用者相关。使用者必须迁移到新版本,更新、测试和发布客户端代码。但这只是直接成本。还有其他间接成本:更新发布新客户端代码存在风险;迁移过程中投入的时间和精力是对其他紧急业务问题的机会成本。即使迁移成本对提供者来说很小,也必须符合使用者的预算。使用者可能会因为新版本带来的业务收益有限而犹豫是否迁移。
Jean-Jacques(JJ)Dubray 描述了三种引导 API 使用者迁移新版本的模式,如图 4.6 所示:
- 结点(Knot)迁移模式
所有 API 使用者(及其生态)绑定到同一个 API 版本,被迫协调一致地迁移到新的版本。JJ 认为,随着版本数量增加,这种迁移模式的成本是最高的,可能高到使每次服务变更都变成战术性调整。
提示:关于 JJ 对 API 迁移成本的数学估算,可见 mng.bz/0GON 。 - 点对点(Point-to-Point)迁移模式
不同版本的 API 服务同时在生产环境中运行,允许 API 使用者根据自身情况选择何时迁移。部分使用者可能永远不迁移,只要现有版本满足需求。与结点模式不同,提供者承担维护多个版本 API 服务的成本,但总体比结点模式成本低。 - 兼容(Compatible)迁移模式
所有使用者都通过同一 API 服务访问,服务提供兼容多个版本的能力。即使使用者依赖不同版本,该服务仍能处理他们的请求。JJ 认为,这种模式的总成本最低。
图中还展示了随着 API 版本数量增长,这三种模式对应成本的变化趋势。

以下是翻译:
一个采用兼容迁移模式的公司例子是 Stripe。Stripe 使用版本变换模块(version change module)来确保 API 服务的最新版本仍能处理来自 API 使用者的向后不兼容请求。这个版本变换模块是一段声明式代码,根据请求的 API 版本,对操作返回的 API 资源进行转换,使其符合预期的 API 版本。版本变换模块与最新版本的 API 服务代码分开维护。在收到最新版本代码生成的 API 响应后,版本变换模块会按逆序应用,从当前版本开始,依次执行转换函数,直到达到目标 API 版本。
这种做法实现了关注点分离,开发者可以专注于向核心代码添加新功能,同时旧版本的支持由独立的版本变换模块负责,将资源转换为兼容旧版本。版本变换模块包含文档说明所执行的转换操作,结合其声明式特性,可以用于自动生成 API 变更日志通知用户。关于 Stripe 兼容迁移模式的更多内容,请见:stripe.com/blog/api-ve... Intercom,详情见:www.intercom.com/blog/api-ve...
版本变换模块有助于降低提供者引入新版本的成本,同时帮助 API 使用者迁移到新代码版本,即使他们仍通过旧 API 访问接口。但这也不是免费的,因为需要编写、测试和维护相关代码。Stripe 大部分版本变换模块是纯函数,只转换响应,但也有一些带有副作用,比如修改数据库中的数据,这类模块封装性差,维护更复杂。
4.11 破坏性变更策略
根据 Hyrum 定律,"当一个 API 拥有足够多的用户时,合同中你所承诺的内容无关紧要:系统的所有可观察行为都会被某些用户依赖"(www.hyrumslaw.com)。这意味着无论你在.xn--6qq96jprcwtpg5fr82apz5a94g/) API 定义中如何声明合同,规模够大时,总会有用户忽视这些声明,反而依赖你的实现细节,因此任何偏离当前实现的改动都会破坏他们的预期。这就是为什么 API 提供者必须公开明确的破坏性变更策略:这是一份文件,阐明哪些改动算作破坏性变更,如何定义稳定的 API,以及期望 API 使用者如何随着 API 演进。
你应通过开发者门户将破坏性变更策略告知 API 使用者。有些组织称之为 API 版本策略页。无论叫什么,这份策略应涵盖:
- 你的 API 版本方案、范围及版本标识符
- 你认为的破坏性与非破坏性变更清单
- 你的 API 稳定性等级
- API 版本发布时间表
REST API 破坏性变更策略示例包括:
- Microsoft "Versioning, Support, and Breaking Change Policies for Microsoft Graph"(mng.bz/KZnj)
- LinkedIn "LinkedIn API Breaking Change Policy"(mng.bz/5lza)
- HubSpot "Breaking Changes on the HubSpot Platform"(mng.bz/9d87)
- Onfido "API versioning policy"(mng.bz/67By)
我已在第4.7至4.9节讲解了 API 版本方案、范围和标识符,下面将介绍破坏性变更、API 稳定等级和版本发布时间表。
4.11.1 定义破坏性与非破坏性变更
定义并公开你认为的破坏性与非破坏性变更清单很重要,这样 API 使用者在构建客户端集成时能参考这份信息。基于清单,你可以用 OpenAPI 差异检测工具或脚本实现自动破坏性变更检测。表4.1列举了破坏性变更的示例,但不同组织对破坏性变更的认定可能不同。例如,我把响应中新增枚举值视为破坏性变更,但 Onfido 和 GitHub 则认为它是非破坏性的。(关于枚举扩展性问题详见4.12节)此外,部分 API 差异工具对于破坏性和非破坏性的判定也不一致。
这两个原因都说明了为何发布你自己的清单很重要,这不仅有助于管理 API 使用者预期,还能确保所用的差异工具符合你的定义,否则需辅以额外脚本。
表4.1 OpenAPI定义文件中破坏性变更示例
API对象 | 变更类别 | 变更详情 |
---|---|---|
路径(Path) | 删除 | 删除一个端点 |
新增 | 新增必需的路径参数 | |
请求(Request) | 新增/增加 | 请求体中新增必需字段新增验证模式新增必需请求头增加数组最小元素数量 |
变更 | 请求头参数由可选改为必需请求体中的可选属性改为必需修改请求参数的验证模式 | |
删除/减少 | 删除枚举值减少数组最大元素数量减少字符串最大长度 | |
响应(Response) | 新增/增加 | 增加响应中数组最大长度新增枚举值(视客户端代码可能破坏兼容) |
变更 | 响应体中必需属性变为可选响应头由必需变为可选 | |
删除/减少 | 删除必需属性删除某种媒体类型删除枚举值删除必需响应头减少响应中数组最小元素数量减少响应字符串最小长度 |
我这里未列出非破坏性变更清单,但你也可以准备类似的清单。一般来说,非破坏性变更是新增操作,比如新增端点、可选请求参数和请求头、新增响应字段和响应头。
4.11.2 API 稳定性
API 稳定性指 API 提供者对某版本未来变更做出的承诺。指定稳定性等级的目的是让 API 使用者了解集成后需要多久更新一次客户端代码,同时便于提供者沟通即将的变更并获取反馈。
Google 在 AIP-181(google.aip.dev/181)中对 API 设计者关于稳定性等级提供指导:
- Alpha --- API 处于快速迭代阶段,允许破坏性变更,用户是容忍快速变动的一小部分已知用户。
- Beta --- API 功能完成并经过公共测试,准备宣布稳定。可向未知且大量用户公开。Beta API 应尽量稳定,但仍可能允许破坏性变更,且必须提供合理的弃用周期让用户迁移。
- Stable --- 稳定 API 必须全面支持,且不得引入破坏性变更,除非因安全或法规需求。
Ghost REST API(ghost.org/docs/conten...
- Experimental --- 活跃开发中的版本,可能包含破坏性变更
- Stable --- 仅允许非破坏性(向前兼容)变更
- Deprecated --- 计划废弃的版本
无论你如何定义稳定性等级,都应在开发者门户明确文档化,方便 API 使用者理解。
4.11.3 API 版本计划
你还应说明计划支持稳定版本的时长,方便使用者升级规划。比如 Shopify 每三个月发布一个新稳定版本,支持至少12个月,保证用户有至少9个月的时间从一个稳定版本测试并迁移到下一个。
提示:了解 Shopify 发布计划请见 mng.bz/oe6M。
API 稳定性沟通还应包括弃用(deprecation)和下线(sunsetting)策略,作为 API 生命周期的一部分。
- 弃用是对继续使用某 HTTP 资源的正式警告,提醒使用者该 API 将来某时不再支持,应开始迁移,不应再依赖该 API 构建新应用。OpenAPI 通过 Deprecated 字段支持弃用,值为布尔型,可设置在操作、参数和模式对象上。
- 下线是在弃用窗口期结束后,API 从服务器移除,不再可用,访问时返回 404 Not Found 错误。
图4.7展示了弃用日期和下线日期的区别。

将 API 下线(sunsetting)属于破坏性变更,因为使用者将无法继续使用该 API。但如果你已向使用者发出弃用通知(deprecation notice)并给予足够时间完成迁移,那么你可能不希望在自动化检测中将下线视为破坏性变更。如果你在 CI/CD 流水线中自动执行破坏性变更检测,计划中的下线不应导致检测失败。
4.12 管理破坏性变更
如你所见,API 版本管理涉及版本方案、范围、稳定性、与使用者沟通及发布时间表等多个方面。而根据 Hyrum 定律,破坏性变更不仅涉及 API 定义合同中的设计,还可能涉及使用者依赖的任何可观察行为。诸如 Tufin/oasdiff 这样的 API 差异检测工具能帮助发现设计层面的破坏性变更,但根据你的代码库和工作流程,可能还需采取其他控制措施。以下是你可以考虑的一些方法,帮助在工作流程中管理破坏性变更。
将 API 版本管理作为一等公民
对新 API,建议即使暂时不使用,也要设计一个版本管理方案。这样,当需要时你可以选择使用。确保所选方案或方案组合能支持你希望的演进范围。同时选择一个对项目合理且使用者易于理解的版本标识符。在 API 中将版本管理作为一等概念,也有助于你在变更日志和 API 文档中解释 API 的变化。
设计可扩展的 API
Bertrand Meyer 的开闭原则(Open-Closed Principle)指出,软件应对扩展开放,对修改关闭。应用到 API 设计,就是说资源设计应允许添加新属性以扩展功能,但不能修改使用者依赖的已有属性。
当最终值列表未知或可能变化时,考虑使用开放式列表替代枚举(enum)。枚举是值固定的属性。客户端集成代码(由开发者或自动化工具基于 OpenAPI 定义生成)可能无法稳健处理新出现的未知枚举值,这可能导致破坏性变更和客户端代码报错。枚举适合值列表固定不变的场景。鉴于 API 演进的特点,早期阶段需求尚未明确时很难保证值列表不变,因此若未来可能扩展列表,建议使用开放式值列表。
例如,避免这样使用枚举:
yaml
properties:
role:
type: string
enum:
- all
- admin
- member
而改为开放式字符串:
yaml
properties:
role:
type: string
Zalando 推荐使用 x-extensible-enum
扩展,为 API 使用者提示当前支持的值列表,示例如下:
yaml
properties:
role:
type: string
x-extensible-enum:
- all
- admin
- member
但采用此方法时,API 使用者需准备好处理枚举扩展,并实现降级逻辑以正确应对未知枚举(详见:mng.bz/ng5K)。API 提供者也应谨慎扩展枚举,不应改变已有枚举值的语义。
当确认值列表不会变更时,可以将字符串列表轻松转为枚举,这不会构成破坏性变更,也能为新的客户端集成提供更好的类型安全。
另一个面向扩展的设计是避免将 JSON 数组作为顶层数据结构。顶层 JSON 数组是指根元素为数组而非对象的 JSON 文档,例如:
css
[ { "name": "Acme Rope Toy", "description": "Acme Rope Toy provides hours of fun for your dog", "price": 100 }, { "name": "Acme Dog Food", "description": "Healthy food for your dog", "price": 50 }]
此类文档无法添加新的顶层属性(如包含分页信息的 meta
或 pagination
对象),否则会引入破坏性变更。若用顶层 JSON 对象包裹数组,则可以做到扩展,例如:
json
{
"data": [
{
"name": "Acme Rope Toy",
"description": "Acme Rope Toy provides hours of fun for your dog",
"price": 100
},
{
"name": "Acme Dog Food",
"description": "Healthy food for your dog",
"price": 50
}
],
"pagination": {
"self": "https://api.acme-pet-supplies.com/v1/catalog/products?cursor=bccf5512",
"prev": "https://api.acme-pet-supplies.com/v1/catalog/products?cursor=0242ac120",
"next": "https://api.acme-pet-supplies.com/v1/catalog/products?cursor=651b1f06"
}
}
因此,建议在 API 请求和响应中,顶层数据结构始终使用 JSON 对象而非数组。
使用开放式列表(如 x-extensible-enum
)和避免顶层 JSON 数组,是设计可扩展 API、减少将来引入破坏性变更风险的有效手段。你的 API 评审流程也可以帮助发现这类设计机会。轻量级的同行评审应关注设计的可扩展性。API 评审将在第5章中详细讨论。
总结
结构化地对比不同版本的 API 定义文件,有助于你明确 OpenAPI 设计迭代过程中发生了哪些变化。
破坏性变更会迫使 API 使用者更新他们的客户端应用。OpenAPI 差异检测工具能够帮助你自动识别不同 API 定义版本间的破坏性变更。
API 版本管理是让你的 API 在保持与现有客户端兼容的同时实现演进的一种方式。你可以选择多种版本方案、范围和标识符,以便沿着最适合你系统的维度演进 API。但要把 API 版本管理视为 API 产品接口的一部分,同时注重易用性。
你的破坏性变更策略应包括你认定的破坏性和非破坏性变更列表、API 版本稳定性等级以及 API 版本发布计划。你应将破坏性变更策略公开发布,以便 API 使用者据此规划他们的集成工作。