目前只是简单的搬运,还没有整理
API应该使用语义化的版本。比如给定版本号 MAJOR.MINOR.PATCH
:
- 当做出不兼容修改的时候,修改 MAJOR 版本号
- 当以向后兼容的方式添加功能时,修改 MINOR 版本号
- 当进行向后兼容的错误修复时,修改 PATCH 版本号
对于 GA 之前的版本,例如 alpha 和 beta,建议在版本号后面附加一个后缀,应该包括预发布版本名称(例如alpha,beta)和预发布版本号。
版本升级示例:
- v1alpha alpha 版本
- v1beta1 beta 版本。因为破坏兼容性可以接受,所以不需要更新版本号
- v1beta1 可信测试版
- v1beta2 可信测试版本迭代
- v1test 带有假数据的可信测试版
- v1 正式发布版
- v1.1beta1 v1小改版的beta版
- v1.1 v1小改版
- v2beta1 新大版本的beta版
- v2 新大版本,正式发布版
minor 和 patch 版本号应该反映在 API 配置和文档中,它们不允许被编码在原始程序包名称中。
API 的新 major 版本不能依赖于以前同样API的主要版本。 API 在了解相关的依赖性和稳定性风险的情况下可以依赖其他 API。 API 必须只依赖于其他API的最新稳定版本。
在一段时间内,同一 API 的不同版本必须能够在客户端应用程序中同时工作。这是为了帮助客户端从旧版本平滑过渡到更新版本的 API。旧的 API 版本应该在其弃用期结束后删除。
1. 向后兼容
确定向后兼容的修改是困难的。以下列表是一份快速参考 向后兼容的修改
- 将 API 接口添加到 API 服务定义
- 向 API 接口添加方法
- 向方法添加 HTTP 绑定
- 向请求消息中添加字段
- 向响应消息中添加字段
- 向枚举添加值
- 添加仅输出的资源字段
向后不兼容的修改
- 删除或重命名服务,字段,方法或枚举值
- 更改 HTTP 绑定
- 更改字段的类型
- 更改资源命名格式
- 更改现有请求的可见行为
- 更改 HTTP 定义中的 URL 格式
- 向资源消息添加读/写字段
兼容性
什么算是一个破坏性(不兼容)的变化并没有明确的定义。这里列出的规则只涉及客户端兼容性。预期API生产者明白在部署方面的要求,包括实现细节的变化。
一般目的是,服务端更新到一个新的minor版本或patch版本不该破坏客户端。可预期的破坏类型有:
- 源代码兼容性:针对1.0编写的代码无法在1.1版本编译
- 二进制兼容性:编译的1.0版本的库在1.1客户端库情况下无法链接/运行。(具体细节取决于客户端平台;在不同情况下有不同表现)。
- 传输兼容性:针对1.0构建的应用程序无法与1.1服务器通信
- 语义兼容性:可以运行,但产生无意义的或令人惊讶的结果
换句话说:旧客户端应该能够兼容同一major版本号的较新的服务器,并且当后者试图更新到新的minor版本(例如利用新功能)时,可以很容易做到。
除了理论上的基于协议的考虑之外,也有出于客户端库生成代码和手写代码的实际考虑。尽可能思考在生成新版本的客户端库过程中会产生哪些变化,针对它们写测试用例,并确保其通过测试。
下面的讨论将原始消息分为三类:
请求消息(例如 GetBookRequest)
响应消息(例如 ListBooksResponse)
资源消息(例如 Book,以及包括在其他资源消息中使用的任何消息)
这些类别具有不同的规则,因为请求消息只能从客户端发送到服务器,响应消息只能从服务器发送到客户端,但通常资源消息是双向发送。特别强调,对可更新的资源,需要考虑其 读取/修改/写入 循环的顺序。
1.1. 向后兼容(非破坏性)的修改
- 给API服务定义添加API接口
从协议的角度来看,这始终是安全的。 唯一需要注意的是,客户端库可能已经在手写的代码中使用了您的新API接口名。 如果新接口与现有接口完全正交,那就没问题(不可能正交); 如果新接口是现有接口的简化版本,则更有可能导致冲突。
- 给API接口添加方法
除非你添加的方法和客户端库已经生成的方法冲突,否则应该没问题。
(举一个破坏性的例子:如果你有一个 GetFoo 方法,C#代码生成器已经创建了GetFoo和GetFooAsync方法.在API接口中添加一个GetFooAsync方法,从客户端角度,这就是一个破坏性的修改。)
- 给方法添加HTTP绑定
假设绑定不引入任何歧义,使得服务器响应以前会拒绝的URL,这就是安全的。 当将现有操作应用于新资源名称模式时,可以这样做。
- 给请求消息添加字段
只要客户端在新版和旧版中对该字段的处理不保持一致,添加请求字段就是兼容的。
最显而易见的反面例子是分页:如果v1.0的API在集合中不包括分页,则不能在v1.1中添加分页,除非新版中的page_size默认为无限大(这通常是一个坏主意)。 否则,希望在单个请求中获取完整结果的v1.0客户端可能会收到不完整的结果,并且不知道结果集包含更多资源。
- 给响应消息添加字段
在不改变其他响应字段的行为的前提下,非资源(例如,ListBooksResponse)的响应消息可以扩展而不必破坏客户端的兼容性。即使会引入冗余,先前在响应中填充的任何字段应继续使用相同的语义填充。
例如,1.0中的查询响应可能包含一个布尔字段contained_duplicates,用来标示某些结果因为重复而被忽略。 在1.1中,我们可能会在duplicate_count字段中提供更详细的信息。 即使从1.1版本的角度来看它是冗余的,仍然必须填充contained_duplicates字段。
- 给枚举(enum)添加值
仅在请求消息中使用的枚举可以自由地扩展新值。例如,使用 资源视图 模式,可以在新的minor版本中添加新视图。客户端从来不需要接收这个枚举,所以它们不必知道它们不关心的值。
对于资源消息和响应消息,缺省假设客户端应该处理他们不知道的枚举值。然而,API生产者应该意识到,编写能正确处理新枚举值的应用程序可能很困难。 API所有者应该在文档里描述当遇到未知枚举值时,预期的客户端行为。
Proto3允许客户端接收不明意义并保留相同值的消息,因此不会中断 读取/修改/写入 周期。 JSON格式允发送一个名称是未知的值,但是服务器端显然不知道客户端是否能正确理解该字段的确切值。JSON客户端可能意识到已经接收了一个未知的值,要么识别出字符串”name”,要么是数字,不可能两者都识别。客户端会在 读取/修改/写入 周期中原封不动的将值返回服务器,因为服务器应该理解这两种格式。
- 给仅输出的资源添加字段
仅由服务器提供的资源实体中可以添加字段。 服务器可以验证请求中的任何客户端提供的值,但是如果省略该值,服务器禁止失败。
## 1.2. 向后不兼容(破坏性)的修改
- 删除或重命名服务,字段,方法或枚举值
从根本上说,如果客户端代码可以引用某些东西,那么删除或重命名它都是不兼容的变化,这时必须修改major版本号。 引用旧名称的代码将导致某些语言(例如C#和Java)在编译期出现故障,并可能导致其他语言的执行时失败或丢失数据。 传输格式兼容性与此无关。 修改HTTP绑定
“修改”在这里特指“删除和添加”。 例如,如果您确定要支持PATCH方法,但是您发布的版本支持PUT方法,或者您使用了错误的自定义方法名称,你可以添加新的绑定,但禁止删除旧绑定,原因同删除一个服务方法一样,都是一个不兼容的变化。
- 修改字段的类型
即使新类型是传输格式兼容的,这也可能会导致客户端库生成的代码发生变化,因此必须增加major版本号。 对于编译型静态语言来说,会容易引入编译错误。
- 修改资源命名格式
资源禁止修改其名称 - 这意味着集合名称不能修改。
与大多数不兼容修改不同,这也影响major版本号:如果预期客户端使用v2.0 API访问在v1.0 API中创建的资源(反之亦然),则应在两个版本中使用相同的资源名称。
更细微地讲,有效资源名称的集合也不应该修改,原因如下:
- 如果变得更加严格,则以前成功的请求现在将失败。
- 如果它变得更宽松,那么基于前面文档做出的客户端可能会不兼容。客户端很可能在其他地方存储资源名称,可能对允许的字符集和名称的长度敏感。或者,客户端可能正在执行自己的资源名称验证。 (例如,在允许更长的EC2资源ID时,亚马逊给客户发出了很多警告,并且提供了一个迁移期)
注意,这样的改变只能在proto文档中可见。因此,当审查CL的兼容性时,仅检查非注释部分修改是不够的。
- 修改现有请求的可见行为
客户端通常依赖于API行为和语义,即使这样的行为没有被明确支持或记录。 因此,在大多数情况下,修改API数据的行为或语义将被消费者视为是破坏性的。 如果行为没有加密隐藏,您应该假设用户已经发现它,并将依赖于它。 例如,用户对AWS EC2资源标识符进行了逆向。
因此,加密分页令牌(即使数据没有啥意义)是一个好主意,以防止用户创建自己的令牌,有潜在的令牌行为改变时可能会不兼容的风险。
- 修改HTTP定义中的URL格式
除了上面列出的资源名称修改外,有两种修改需要考虑:
- 自定义方法名称:虽然不是资源名称的一部分,但自定义方法名称是提交到REST客户端的URL的一部分。 修改自定义方法名称不应破坏gRPC客户端,但公共API必须假定它们具有REST客户端。
-
资源参数名称:从v1/shelves/{shelf}/books/{book}修改为v1/shelves/{shelf_id}/books/{book_id}不会影响替代资源名称,但可能会影响代码生成。
- 给资源消息添加 读取/写入 字段
客户端将经常执行 读取/修改/写入 操作。 大多数客户端不会为他们不知道的字段提供值,而且proto3也不支持。 您可以指定任何缺少消息类型(而不是原始类型)的字段,这意味着更新不适用于这些字段,也使得从实体中显式移除该字段值变得更加困难。 原始类型(包括字符串和字节)不能这样处理,因为在显式指定int32字段为0和没有指定之间,proto3的表现没有什么不同。
如果所有更新都是使用字段掩码执行,这就不是问题,因为客户端不会隐式地覆盖其不知道的字段。 然而,这将是一个不寻常的API决策:大多数API允许“整个资源”更新。