兼容性

本页面详细介绍了版本控制部分提供的重大更改和非重大更改的列表。

何为重大(不兼容)更改,这个问题的答案并不完全明确。此处的指导说明被视为每个可能更改的指示性列表而非完整列表。

此处列出的规则仅涉及客户端兼容性。API 提供方应该了解自己在部署方面的要求,包括实现详细信息的更改。

总体目标是将服务更新到新的次要版本或应用补丁时客户端不应该受影响。尚在研究的影响类型包括:

  • 源兼容性:针对 1.0 编写的代码无法针对 1.1 进行编译
  • 二进制兼容性:针对 1.0 编译的代码无法针对 1.1 客户端库来链接/运行。(详细情况取决于客户端平台;这个问题在不同情况下有所不同。)
  • 线路兼容性:针对 1.0 构建的应用程序无法与 1.1 服务器通信
  • 语义兼容性:一切都在运行,但产生了意想不到的或出乎意料的结果

从另一个角度说:旧客户端应该能与使用同一主要版本号的较新服务器结合使用,当其要更新到新的次要版本时(例如利用新功能),应该可以轻松做到。

除了从基于协议的理论方面来考虑,由于存在涉及生成代码和手写代码的客户端库,因此存在实际考虑因素。测试您正在研究的更改时,应尽可能通过生成新版本的客户端库来测试,并确保其测试仍能通过。

下面的讨论内容将原型消息分为三类:

  • 请求消息(例如 GetBookRequest
  • 响应消息(例如 ListBooksResponse
  • 资源消息(例如 Book,并包括其他资源消息中使用的任何消息)

这些类别的消息具有不同的规则,因为请求消息仅从客户端发送到服务器,响应消息仅从服务器发送到客户端,但资源消息通常可以双向传输。 特别是,可以更新的资源需要从读取/修改/写入周期方面来考虑。

向后兼容的(非重大)更改

向 API 服务定义添加 API 接口

从协议的角度来看,这始终比较安全。唯一需要注意的是,客户端库可能已经使用了手写代码中的新 API 接口名称。如果您的新接口与现有接口完全正交,则不太可能实现;如果它是现有接口的简化版本,则更有可能导致冲突。

向 API 接口添加方法

除非您添加的方法与客户端库中已生成的方法发生冲突,否则这应该没问题。

(可能造成重大后果的例子:如果您有 GetFoo 方法,C# 代码生成器已经创建了 GetFooGetFooAsync 方法。因此,从客户端库的角度来看,在 API 接口中添加 GetFooAsync 方法将是一个重大更改。)

向方法添加 HTTP 绑定

假设绑定没有引入任何歧义,让服务器响应之前拒绝的网址就是安全的。将现有操作应用于新资源名称模式时,可以执行此操作。

向请求消息添加字段

添加请求字段可以是非重大更改,前提是未指定该字段的客户端将在新版本中采用与旧版本相同的处理方式。

可能错误地执行此操作的最明显示例是使用分页:如果 API 的 v1.0 不包含集合的分页,则无法在 v1.1 中添加它,除非将默认的 page_size 视为无限(这通常是一个坏主意)。否则,希望通过单个请求获得完整结果的 v1.0 客户端可能只收到部分结果,而且不会意识到该集合包含更多资源。

向响应消息添加字段

并非资源(例如 ListBooksResponse)的响应消息可在不影响客户端的情况下进行扩展,前提是这样不会改变其他响应字段的行为。之前在响应中填充的任何字段都应继续使用相同的语义填充,即使这会引入冗余也如此。

例如,在 1.0 版中的查询响应可能包含 contained_duplicates 的布尔字段,这表示某些结果由于复制而省略。在 1.1 版中,我们可能会在 duplicate_count 字段提供更详细的信息。尽管它在 1.1 版本中是多余的,但 contained_duplicates 字段仍必须填充。

向枚举添加值

只能在请求消息中使用的枚举可以自由扩展以包含新元素。例如,使用资源视图模式可在新的次要版本中添加新视图。客户永远不需要接收这个枚举,因此他们不必知道他们不关心的值。

对于资源消息和响应消息,默认假设客户端应该处理他们不知道的枚举值。但是,API 提供方应该知道编写应用来正确处理新的枚举元素可能很困难。API 所有者应该在遇到未知枚举值时记录预期的客户端行为。

借助 Proto3,客户端可以接收它们不知道的值并重新序列化保持相同值的消息,这样才不会影响读取/修改/写入周期。使用 JSON 格式可在值“名称”未知的情况下发送数值,但服务器通常不知道客户端是否真的知道特定值。因此,JSON 客户端可能知道它们已收到以前未知的值,但它们只会看到名称或数字 - 他们不会同时知道这两者。 在读取/修改/写入循环中将相同的值返回到服务器,此时不应修改该字段,因为服务器应该理解这两种形式。

添加仅限输出的资源字段

可以添加只能由服务器提供的资源实体中的字段。服务器可以验证请求中任何客户端提供的值是否有效,但不能在该值省略时失败。

向后不兼容的(重大)的更改

移除或重命名服务、字段、方法或枚举值

从根本上看,如果客户端代码可能引用某些内容,对其执行移除或重命名操作就是重大更改,必须通过新的主要版本进行。引用旧名称的代码,对于有些语言(例如 C#和 Java)会导致编译失败,对于其他语言则可能导致执行失败或数据丢失。传输格式兼容性与此无关。

更改 HTTP 绑定

此处的“更改”实际上是“删除和添加”。例如,如果您确定确实要支持 PATCH,但您发布的版本支持 PUT,或者您使用了错误的自定义动词名称,则可以添加新绑定,但不能因为相同原因而删除旧绑定,因为移除服务方法是一个重大更改。

更改字段的类型

即使新类型与传输格式兼容,这也可能更改客户端库生成的代码,因此必须通过新的主要版本进行。对于已编译的静态类型语言,这很容易引入编译时错误。

更改资源名称格式

资源不得更改其名称 - 这意味着不能更改集合名称。

与大多数重大更改不同,这也会影响主要版本:如果客户端可以使用 v2.0 访问在 v1.0 中创建的资源(反之亦然),则应在两个版本中使用相同的资源名称。

较容易忽略的是,由于以下原因,有效资源名称集也不应更改:

  • 如果它的限制变得更严格,之前成功的请求现在将失败。
  • 如果它没有之前记录的限制严格,基于先前文档做出假设的客户端可能无法正常工作。客户很可能采用对允许的字符集和名称长度敏感的方式,将资源名称存储在其他位置。或者,客户很可能执行自己的资源名称验证以遵循文档说明。(例如,在开始支持更长的 EC2 资源 ID 之前,亚马逊为客户提供了大量警告并且有一个迁移期。)

请注意,此类更改可能仅在原型文档中可见。 因此,在审核 CL 是否损坏时,仅查看非评论更改并不够。

更改现有请求的可见行为

客户通常依赖 API 行为和语义,即使此类行为没有得到明确支持或记录。因此,在大多数情况下,更改 API 数据的行为或语义造成的影响将被视为使用者的责任。如果行为未以加密方式隐藏,则假设用户已发现并将依赖此行为。

由于这个原因(即使数据很无趣),对分页令牌加密也是一个好主意,可以防止用户创建自己的令牌,以及在令牌行为发生更改时影响令牌。

更改 HTTP 定义中的网址格式

除了上面列出的资源名称更改之外,此处需要考虑两种更改:

  • 自定义方法名称:虽然并非资源名称的一部分,但自定义方法名称是 REST 客户端发布到的网址的一部分。更改自定义方法名称不应该影响 gRPC 客户端,但公共 API 必须假定它们具有 REST 客户端。
  • 资源参数名称:从 v1/shelves/{shelf}/books/{book} 更改为v1/shelves/{shelf_id}/books/{book_id} 不会影响已替代的资源名称,但可能会影响代码生成。

向资源消息添加读取/写入字段

客户端通常会执行读取/修改/写入操作。大多数客户端不会为它们不知道的字段提供值,特别是 proto3,它不支持此操作。您可以指定消息类型(而不是原始类型)的任何缺失字段都表示更新未应用于这些字段,但这使得从实体中显式移除此类字段值变得更加困难。 原初类型(包括 stringbytes)根本无法采用这种方式处理,因为在 proto3 中,将 int32 字段明确指定为 0 与完全不指定没有区别。

如果使用字段掩码执行所有更新,这就不是问题,因为客户端不会隐式覆盖它不知道的字段。但是,这个 API 决策并不寻常:大多数 API 都支持“整个资源”更新。