AIP-162 资源修订
编号 | 162 |
---|---|
原文链接 | AIP-162: Resource Revisions |
状态 | 草案 |
创建日期 | 2023-09-01 |
更新日期 | 2019-09-17 |
一些API需要保留资源修订历史,用户可以推断资源在历史上的状态。这有几个原因:
- 用户希望能够回滚到先前的修订,或者比较与先前修订的差异。
- API创建的数据可能是从某个时间点的资源派生出来的。此时需要为资源创建快照,供以后参考。
注意 我们使用“修订”(revision)指代特定资源的历史参考,并有意避免使用“版本”(version)一词,后者指的是API整体的版本。
指南
API 可以 存储资源的修订历史。用途示例包括:
- 需要通过API发布资源的历史修订。避免客户自己编写API,存储和检索修订历史的成本。
- 资源依赖于另外一个资源的历史修订。
- 需要表示资源状态的变化历史。
实现带有修订历史的资源的API时, 应当 将资源修订抽象为资源的内嵌集合。有时修订集合也可以是顶级集合,例外情况包括:
- 如果资源修订的生命周期比资源自身更长。换句话说,资源修订在资源删除后仍然存在。
message BookRevision {
// The name of the book revision.
string name = 1;
// The snapshot of the book
Book snapshot = 2
[(google.api.field_behavior) = OUTPUT_ONLY];
// The timestamp that the revision was created.
google.protobuf.Timestamp create_time = 3
[(google.api.field_behavior) = OUTPUT_ONLY];
// Other revision IDs that share the same snapshot.
repeated string alternate_ids = 4
[(google.api.field_behavior) = OUTPUT_ONLY];
}
- 消息 必须 被注解为资源(AIP-123)。
- 消息名字 必须 命名为
{ResourceType}Revision
。 - 资源修订 必须 包含类型为资源自身,名字为
snapshot
的域。snapshot
的值 必须 是资源自身在修订创建时的状态。
- 资源修订 必须 包含
create_time
域(参考AIP-142)。 - 资源修订 可以 包含重复域
alternate_ids
,包含识别修订的资源标识别名列表(例如latest
)。
创建修订
根据资源的不同,不同的API可能采用不同策略:
- 资源自身发生变化时创建修订
- 重要系统状态变化时创建修订
- 收到特定请求时创建新修订
API 可以 使用这些策略中的任何一种。API 必须 在文档中记录修订创建策略。
修订的资源名字
引用资源的特定修订时,子集合名字 必须 是 revisions
。资源修订名字的格式为 {resource_name}/revisions/{revision_id}
。例如:
publishers/123/books/les-miserables/revisions/c7cfa2a8
服务器设定的别名
服务 可以 保留特定标识作为别名(如 latest
)。它们是只读的,由服务器管理。
GET /v1/publishers/{publisher}/books/{book}/revisions/{revision_id}
- 如果存在
latest
标识, 必须 表示最近创建的修订。publishers/{publisher}/books/{book}/revisions/latest
和publishers/{publisher}/books/{book}
的内容可能不同,最新修订可能与资源的当前状态不同。
用户设定的别名
API 可以 提供一种机制,允许用户通过自定义方法“alias”为修订设定别名:
rpc AliasBookRevision(AliasBookRevisionRequest) returns (Book) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*/revisions/*}:alias"
body: "*"
};
}
message AliasBookRevisionRequest {
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
type: "library.googleapis.com/BookRevision"
}];
// 要别名的修订ID,例如 ~CURRENT~ 或语义修订。
string alias_id = 2 [(google.api.field_behavior) = REQUIRED];
}
- 请求消息 必须 有
name
域:- 必须 被 注解为必需域。
- 必须 标识其引用的资源类型。
- 请求消息 必须 有
alias_id
域:- 必须 被注解为必需域。
- 如果用户使用现有
alias_id
调用方法,方法 必须 成功执行,将别名指向所要求的修订。这允许用户编写针对特定别名(如published
)的代码,修订也可以在不修改代码的情况下变更。
回滚
具有修订历史的资源的一个常用场景是能够回滚到特定修订。API 应当 通过自定义方法 Rollback
处理这个问题:
rpc RollbackBook(RollbackBookRequest) returns (BookRevision) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*/revisions/*}:rollback"
body: "*"
};
}
- 必须 使用
POST
HTTP 动词。 - 应当 返回资源修订。
message RollbackBookRequest {
// 书籍应回滚到的修订。
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
type: "library.googleapis.com/BookRevision"
}];
}
- 消息 必须 有
name
域,引用作为回滚目标的资源修订。- 域 必须 被注解为必需域。
- 域 必须 标识其引用的资源类型。
子资源
具有修订历史的资源 可以 包含子资源。此时有两种可能的变体:
- 每个子资源都是资源自身整体的子资源。
- 每个子资源是单个资源修订的子资源。
API 不应 包含多个级别的资源修订,因为这很快就会变得难以推理。
标准方法
任何标准方法 必须 实现相应的AIP(AIP-131、AIP-132、AIP-133、AIP-134、AIP-135),并具有以下附加行为:
- List方法:默认情况下,应答返回的修订列表 必须 按时间倒序排列。用户可以提供
order_by
来代替默认行为。 - 如果修订支持别名,使用别名作为资源名字(如
revisions/1.0.2
)的Delete方法 必须 删除别名,而非资源自身。
由于修订是嵌套在资源中的,另请参考级联删除。
理由
将修订抽象为内嵌集合
将修订作为内嵌集合中的资源,让修订成为一等公民。
- 修订可以提供标准的Get、List和Delete方法。
- 保留了在资源消息之外,向修订增加新域的灵活性。
标记为别名
以前存在 tag
概念,与别名概念重复,为减少AIP复杂性,合并重复术语。
资源配置作为只输出域
虽然可以让修订在Create方法中接受资源配置,但这样做将允许用户提交实际上没有存在过的资源配置。
使用 OUTPUT_ONLY
域,并要求修订表示资源在修订创建时的状态,消除了这个问题。
历史
从集合扩展切换到子集合
在2023年9月,修订被抽象为内嵌资源集合。此前修订更像是使用 @
符号进行扩展的资源。列出和删除修订是资源集合的自定义方法。使用Get方法来检索资源或资源修订。
旧方案的主要优势在于允许资源引用无缝地引用资源或资源修订。
但也有几个缺点:
- 列出修订是资源集合的自定义方法(:listRevisions)
- 删除修订是资源集合的自定义方法
- 在API发现文档中不可见
- 资源标识不能使用
@
最终修改了指南,让资源修订像资源一样,减少了用户认知负担,允许面向资源客户端容易的List、Get、Create和Update资源修订。
使用资源标识而非标签
在以前的设计中,修订有单独的标识符,称为 tag,保存在修订中。
标签实际上是影子资源标识,要求方法按照标签值创建、获取和过滤修订。
通过将标签概念合并到修订标识中,用户不再需要熟悉另外的检索和标识符方法。
修订记录
- 2023-09-01 将AIP更新为子集合。
- 2021-04-27 添加删除修订方法返回资源指南。