翻译自 API Design Guide - Compatibilitynode
本章提供了有关版本控制部分中给出的破坏和保持兼容性修改的详细说明。api
并不老是绝对清楚什么是不兼容的修改,这篇指南 应该(should) 被当成参考性的,而不是覆盖到全部状况。安全
下面列出的这些规则只涉及客户端兼容性,默认 API 做者了解部署(包括实现细节的变化)的需求。ide
通常的目标是服务端升级 minor 或 patch 不能影响客户端的兼容性:测试
代码兼容:针对 1.0 编写的代码在 1.1 上编译失败ui
二进制兼容:针对 1.0 编译的代码与 1.1 客户端的库连接/运行失败(具体的细节依赖客户端,不一样状况有不一样变化)google
协议兼容:针对 1.0 构建的程序与 1.1 服务端通讯失败加密
语义兼容:全部组件都能运行但产生意想不到的结果翻译
简而言之:旧的客户端应该与相同 major 版本的新服务端正常工做,而且可以轻松地升级到新的 minor 版本。版本控制
因为客户端使用了自动生成和手写的代码,除了理论上的基于协议的考虑,还有一些实际的问题。经过生成新版本的客户端库来测试你的修改,并保证测试经过。
下面的讨论将 proto 信息分为三类:
请求信息(例如 GetBookRequest
)
响应信息(例如 ListBooksResponse
)
资源信息(例如 Book
,包括在其余资源消息中使用的任何消息)
这三类有不一样的规则,例如请求信息只会从客户端发送到服务端,响应信息只会从服务端发送到客户端,但资源信息通常会在二者之间互相发送。尤为是可被修改的资源须要根据读取/修改/写入的循环来考虑。
从协议的角度看,这种修改老是安全的。惟一须要考虑的是客户端库可能已经经过手写的代码使用了新 API 接口的名字。若是新接口与其它彻底正交,这种状况不太可能发生。若是是已存接口的简化版本,则极可能引发冲突。
除非添加了一个与现有客户端库中方法冲突的方法,这种修改没有问题。
一个会破坏兼容性的例子:若是有 GetFoo
方法,C# 代码生成器已经建立了 GetFoo
和 GetFooAsync
方法。所以从客户端角度来看,在 API 接口中添加 GetFooAsync
方法将会破坏兼容性。
假设绑定没有引入任何歧义,使服务端响应之前被拒绝的 URL 是安全的。当将现有操做应用于新的资源名称时,可能(may) 会这样作。
添加请求字段能够是兼容的,只要不指定该字段的客户端在新版本中与旧版本表现相同。
会致使错误的最明显例子是分页:若是 API 的 v1.0 版本不支持,除非 page_size
默认值是无穷大(这样是很差的)才能在 v1.1 中加入分页。不然 v1.0 的客户端本来但愿经过一次请求取得全部结果,但实际只能取到一部分。
只要不改变其余响应字段的行为,就能够扩展不是资源的响应消息(例如ListBooksResponse),而不会破坏兼容性。即便致使冗余,任何在旧的响应消息中的字段也应该存在于新的响应中并保持它原来的语义。
例如,1.0 中的一个查询请求的响应有 bool 类型的字段 contained_duplicates
来指示由于重复而忽略掉的结果。在 1.1 中,咱们在 duplicate_count
字段中提供更详细的信息,尽管从 1.1 版原本看是多余的,但 contained_duplicates
字段 必须(must) 要保留。
只在请求信息中使用的枚举类型能够自由扩展来添加新元素。例如,使用资源视图时,新的视图可以添加到新 minor 版本中。客户端历来不须要接收此枚举,因此也不须要关心它。
对于资源消息和响应消息,默认假设客户端应该处理它意识不到的枚举值。可是 API 做者应该意识到编写可以正确处理新枚举值的代码多是困难的。应该(should) 在文档中记录当遇到未知枚举值时客户端的指望行为。
proto3 容许客户端接收它们不关心的值而且当执行从新序列化消息时会保持值不变,因此这样就不会打破读取/修改/写入循环的兼容性。JSON 格式容许发送数值,其中该值的“名称”是未知的,可是服务端一般不会知道客户端是否真正知道特定值。所以 JSON 客户端可能知道它们已经收到了之前对他们未知的值,但他们只会看到名称或数字而不是两个都有。在读取/修改/写入循环中将相同的值返回给服务端不该该修改这个值,由于服务端应该理解这两种形式。
能够(may) 添加仅由服务端提供的资源实体中的字段。服务端 能够(may) 验证请求中的值是否有效,可是若是该值被省略则 必定不能(must not) 失败。
从根本上说,若是客户端代码使用了某些字段,那么删除或重命名它将会破坏兼容性,而且 必须(must) 增长 major 版本号。引用旧名称的一些语言(如 C# 和 Java)在编译时会失败, 另外一些语言会引发运行时异常或数据丢失。协议格式的兼容性在这里是可有可无的。
这里的修改
实际指删除
和添加
。例如,你想要支持 PATCH,但已发布的版本支持 PUT,或者已经使用了错误的自定义动词,你 能够(may) 添加新的绑定,可是 必定不要(must not) 移除旧的,由于和删除服务的方法同样会破坏兼容性。
尽管新类型是协议兼容的,可以改变客户端库自动生成的代码,所以 必须(must) 要升级 major 版本。会致使须要编译的静态类型的语言在编译期就发生错误。
资源 必定不能(must not) 修更名字-这意味着集合名不能被修改。
不像其余大多数破坏兼容性的修改,这会影响 major 版本号:若是客户端指望使用 v2.0 访问在 v1.0 中建立的资源(或反过来),则应该在两个版本中使用相同的资源名称。
对资源名的验证也 不该该(should not) 改变,缘由以下:
若是验证变严格,以前成功能请求如今可能会失败
若是比以前文档中记录的验证要宽松,依据以前文档的客户端可能会被破坏。客户端极可能在其余地方保存了资源名,而且对字符集和名字的长度敏感。或者,客户端可能会执行本身的资源名称验证来保持与文档一致。(例如,当开始支持 EC2 资源的长 ID 时,亚马逊向用户发出了许多警告并提供了迁移的时间)
请注意这样的修改只能在 proto 的文档中可见。所以当评审 CL 时审查除注释外的修改是不够的。
客户端老是依赖 API 的行为和语义,即便没有明确支持或记录此行为。由于在大多数状况下修改 API 的行为和语义在客户端看来是破坏性的。若是某行为不是加密隐藏的,你 应该(should) 假设用户已经依赖它了。
由于这个缘由加密分页 token 是个好主意,以防止用户建立本身的 token,以及防止当 token 行为发生变化时可能带来的不兼容性。
除了上面列出的资源名称的变化,这里还要考虑两种类型的修改:
自定义方法名:虽然不是资源名称的一部分,但自定义方法名称是 REST 客户端 POST 请求 URL 的一部分。更改自定义方法名称不该该破坏 gRPC 客户端,可是公共 API 必须假定它们具备 REST 客户端。
资源参数名:从 v1/shelves/{shelf}/books/{book}
到 v1/shelves/{shelf_id}/books/{book_id}
的修改不会影响替代的资源名称,但可能会影响代码生成。
客户端会常常执行读取/修改/写入的操做。大多数客户端不支持它们意识不到的字段值,特别是 proto3 不支持。你能够指定任意消息类型(而不是原始类型)中缺失的字段表示更新时不会被修改,但这样使删除这样的字段变的困难。原始类型(包括 string
和 bytes
)不能简单地使用这种方法,由于明确地设置 int32
的值为 0 和不对它设置值在 proto3 中并无区别。
使用字段掩码来进行全部更新操做不会有问题,由于客户端不会隐式覆盖其不知道的字段。然而这是一个不寻常的决定,由于大部分 API 容许所有资源被更新。