AIP-180 向后兼容
编号 | 180 |
---|---|
原文链接 | https://google.aip.dev/180 |
状态 | 批准 |
创建日期 | 2019-07-23 |
更新日期 | 2019-07-23 |
API本质上是同用户的契约,用户经常按照API编写代码,部署到生产服务,期望代码正常工作(除非API稳定级别另有要求)。因此,了解什么样的变更是向后兼容的,什么样是不向后兼容的,非常重要。
指南
服务的次要版本或补丁更新 不得 破坏现有的客户端代码。旧客户端 必须 能够与较新的服务器(主版本号相同)一起工作。
重要 很难弄清楚每一个变更是否兼容。 应当 将本指南看作是指示性的,而非所有变更的全面列表。
有三种不同类型的兼容性需要考虑:
- 源代码兼容性:按照早期版本编写的代码 必须 能够按照新版本编译,并成功运行新版本的客户端库。
- 传输兼容性:按照早期版本编写的代码 必须 能够正确与较新的服务器通信。换句话说,不仅输入和输出兼容,序列化和反序列化也预期保持一致。
- 语义兼容性:按照早期版本编写的代码 必须 继续符合大多数理性开发者的预期。(然而在实践中可能很难,有时确定用户所期望的内容涉及主观判断。)
注意 这里的指南通常假定使用protocol buffer和JSON作为传输格式。其他传输格式可能存在稍微不同的规则。
注意 本指南假定API会被使用不同编程语言的消费者调用,且无法控制消费者更新的时间和方式。对于使用范围受限的API(例如,调用API的客户端代码由生产API的同一个团队开发,或部署方式可以强制更新)应仔细考虑其具体的兼容性要求。
添加组件
通常在同一个主版本中 可以 向API添加新组件(接口、方法、消息、域、枚举或枚举值)。
但在操作时请遵守以下准则:
- 按照早期接口编写的代码(因此尚不知道新组件) 必须 继续按照与以往相同的方式处理。
- 不得 向现有请求消息或资源添加新的必需域。
- 任何由客户端填写的域 必须 存在与引入该域之前一致的默认行为。
- 任何之前由服务器填写的域 必须 继续填写,即使产生冗余。
- 特别对于枚举值,请注意用户代码可能无法顺利的处理新值。
- 只用于请求消息的枚举 可以 自由添加枚举值。
- 应答消息或资源中的枚举,如果计划在将来添加新值, 应当 在文档中记录。此时 可以 添加枚举值,但 应当 适度谨慎。
注意 添加与现有组件密切相关的新组件(例如,当存在
string foo
域时添加string foo_value
域),可能遇到生成代码冲突的情况。API生产者 应当 了解他们或用户可能使用的工具中存在的细微差别(工具开发者 应当 尽可能避免这些差别)。
移除或更名组件
在同一个主版本中,现有组件(接口、方法、消息、域、枚举或枚举值) 不得 从API中移除。移除组件是不向后兼容的变更。
重要 更名组件在语义上等同于“移除并添加”。需要执行此类变更时,服务 可以 添加新组件,但 不得 移除现有组件。若将导致允许用户为同一语义概念设定冲突的值,接口行为 必须 明确指定。
在文件间移动组件
现有组件 不得 在文件间移动。
将组件从一个proto文件移动到同一个包下另外的proto文件是传输兼容的,但是为C++或Python等语言生成的代码将产生破坏性变更,因为 import
和 #include
不再指向正确的代码位置。
移入oneofs
现有域 不得 移入或移出oneof。这对Go protobuf插桩是不向后兼容变更。
更改域类型
不得 更改现有域和消息的类型,即使新类型是传输兼容的。因为类型变更会以破坏性方式改变生成的代码。
更改资源名字
不得 更改资源名字。
与一般的破坏性变更不同,资源名字会影响多个主版本:为了让客户端可以使用v2.0接口访问在v1.0接口中创建的资源,或者反过来,两个版本 必须 使用相同的资源名字。
更进一步,有效资源名字集合(译注:即有效资源名字规则) 不应 改变,原因如下:
- 如果资源名字格式变得更严格,以往成功的请求将会失败。
- 如果资源名字格式变得比之前的约定更宽松,基于早期文档开发的代码可能失效。用户很可能将资源名字存储在其他地方,存储方式可能对字符集或名字长度存在限制。另外用户可能会自行验证资源名字,以遵守文档。
- 例如,亚马逊在开始支持更长的EC2资源ID时,向客户发出了大量警告,并设置了迁移期。
语义变更
代码通常依赖于API行为和语义, 即使行为没有得到明确的维护或在文档中记录 。因此API 不得 以可能破坏合理用户代码的方式更改可见行为和语义,此类更改对这些用户来说是破坏性的。
注意 这确实涉及一定程度的主观判断;并非每一个变更都很容易确定是否会影响用户。而对本指南的过度解读看起来会阻止 任何 更改(这不是本文的意图)。
不得更改默认值
默认值是客户端未设定时,服务器为资源分配的值。本节仅适用于资源域中的静态默认值,不适用于动态默认值,例如资源的默认IP地址。
更改默认值被视为破坏性更改, 不得 进行。资源的默认行为由其默认值决定,这 不得 在次要版本之间更改。
例如:
message Book {
// google.api.resource and other annotations and fields
// The genre of the book
// If this is not set when the book is created, the field will be given a value of FICTION.
enum Genre {
UNSPECIFIED = 0;
FICTION = 1;
NONFICTION = 2;
}
}
更改为:
message Book {
// google.api.resource and other annotations and fields
// The genre of the book
// If this is not set when the book is created, the field will be given a value of NONFICTION.
enum Genre {
UNSPECIFIED = 0;
FICTION = 1;
NONFICTION = 2;
}
}
将产生破坏性变更。
序列化默认值
API 不得 更改具有默认值的域的序列化方式。例如,如果域的值等于默认值时,不会出现在应答中。那么序列化方式 不得 修改为包含具有默认值的域。客户端可能将资源中域的存在与否看作某种语义,因此 不得 在次要版本中修改对未设定值的序列化方式。
考虑下面的proto,其中 wheels
的默认值为 2
:
// A representation of an automobile
message Automobile {
// google.api.resource and other annotations and fields
// The number of wheels on the automobile.
// The default value is 2, when no value is sent by the client.
int wheels = 2;
}
首先,当 wheels
的值为 2
时,proto序列化为如下JSON:
{
"name": "my-car"
}
然后,API服务更改序列化,在值等于默认值 2
时仍包含 wheel
,如下所示:
{
"name": "my-car",
"wheels": 2
}
这在主版本内产生了不向后兼容变更。
进一步阅读
- 关于域行为兼容性,请参考AIP-203。
- 关于分页兼容性,请参考AIP-158。
- 关于耗时操作兼容性,请参考AIP-151。
- 关于理解稳定级别和预期,请参考AIP-181。
- 关于客户端库资源名字解析兼容性,请参考AIP-4231
- 关于客户端库方法签名兼容性,请参考AIP-4232
- 关于域存在性变更兼容性,请参考AIP-149。
- 关于资源类型兼容性,请参考AIP-123。
修订记录
- 2024-08-07 添加对资源类型兼容性的引用。
- 2024-06-05 添加对域存在性兼容性的引用。
- 2023-07-26 添加对域行为兼容性的引用。
- 2023-07-26 添加关于具有受限客户端API的注释。
- 2022-08-11 添加“在文件间移动组件”部分。
- 2022-06-01 添加更多与其他AIP的兼容性问题的链接。
- 2019-12-16 明确将现有域移动到oneofs中是破坏性的。