AIP-162
Resource Revisions
Some APIs need to have resources with a revision history, where users can reason about the state of the resource over time. There are several reasons for this:
- Users may want to be able to roll back to a previous revision, or diff against a previous revision.
- An API may create data which is derived in some way from a resource at a given point in time. In these cases, it may be desirable to snapshot the resource for reference later.
Note: We use the word revision to refer to a historical reference for a particular resource, and intentionally avoid the term version, which refers to the version of an API as a whole.
Guidance
APIs may store a revision history for a resource. Examples of when it is useful include:
- When it is valuable to expose older versions of a resource via an API. This can avoid the overhead of the customers having to write their own API to store and enable retrieval of revisions.
- Other resources depend on different revisions of a resource.
- There is a need to represent the change of a resource over time.
APIs implementing resources with a revision history should abstract resource revisions as nested collection of the resource. Sometimes, the revisions collection can be a top level collection, exceptions include:
- If resource revisions are meant to have longer lifespan than the parent resource. In other words, resource revisions exist after resource deletion.
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];
}
- The
message
must be annotated as a resource (AIP-123). - The
message
name must be named{ResourceType}Revision
. - The resource revision must contain a field with a message type of the
parent resource, with a field name of
snapshot
. - The value ofsnapshot
must be the configuration of the parent at the point in time the revision was created. - The resource revision must contain a
create_time
field (see [AIP-142][]). - The resource revision may contain a repeated field
alternate_ids
, which would contain a list of resource IDs that the revision is also known by (e.g.latest
)
Creating Revisions
Depending on the resource, different APIs may have different strategies for
- Create a new revision any time that there is a change to the parent resource
- Create a new revision when important system state changes
- Create a new revision when specifically requested
APIs may use any of these strategies. APIs must document their revision creation strategy.
Resource names for revisions
When referring to specific revision of a resource, the subcollection name
must be named revisions
. Resource revisions have names with the format
{resource_name}/revisions/{revision_id}
. For example:
publishers/123/books/les-miserables/revisions/c7cfa2a8
Server-specified Aliases
Services may reserve specific IDs to be aliases (e.g.
latest
). These are read-only and managed by the service.
GET /v1/publishers/{publisher}/books/{book}/revisions/{revision_id}
- If a
latest
ID exists, it must represent the most recently created revision. The content ofpublishers/{publisher}/books/{book}/revisions/latest
andpublishers/{publisher}/books/{book}
can differ, as the latest revision may be different from the current state of the resource.
User-Specified Aliases
APIs may provide a mechanism for users to assign an alias ID to an existing revision with a custom method "alias":
rpc AliasBookRevision(TagBookRevisionRequest) 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"
}];
// The ID of the revision to alias to, e.g. `CURRENT` or a semantic
// version.
string alias_id = 2 [(google.api.field_behavior) = REQUIRED];
}
- The request message must have a
name
field:- The field must be annotated as required.
- The field must identify the resource type that it references.
- The request message must have a
alias_id
field:- The field must be annotated as required.
- If the user calls the method with an existing
alias_id
, the request must succeed and the alias will be updated to refer to the provided revision. This allows users to write code against a specific alias (e.g.published
) and the revision can change with no code change.
Rollback
A common use case for a resource with a revision history is the ability to roll
back to a given revision. APIs should handle this with a Rollback
custom
method:
rpc RollbackBook(RollbackBookRequest) returns (BookRevision) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*/revisions/*}:rollback"
body: "*"
};
}
- The method must use the
POST
HTTP verb. - The method should return a resource revision.
message RollbackBookRequest {
// The revision that the book should be rolled back to.
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
type: "library.googleapis.com/BookRevision"
}];
}
- The request message must have a
name
field, referring to the resource revision whose configuration the resource should be rolled back to.- The field must be annotated as required.
- The field must identify the resource type that it references.
Child resources
Resources with a revision history may have child resources. If they do, there are two potential variants:
- Child resources where each child resource is a child of the parent resource as a whole.
- Child resources where each child resource is a child of a single revision of the parent resource.
APIs should not include multiple levels of resources with revisions, as this quickly becomes difficult to reason about.
Standard methods
Any standard methods must implement the corresponding AIPs (AIP-131, AIP-132, AIP-133, AIP-134, AIP-135), with the following additional behaviors:
- List methods: By default, revisions in the list response must be ordered
in reverse chronological order. User can supply
order_by
to override the default behavior. - If the revision supports aliasing, a delete method with the resource name
of the alias (e.g.
revisions/1.0.2
) must remove the alias instead of deleting the resource.
As revisions are nested under the resource, also see cascading delete.
Rationale
Abstract revisions as nested collection
Revisions being resources under nested collection make revisions a first class citizen.
- Revisions can offer standard get, list, and delete methods.
- It retains the flexibility of extending new fields to revision in addition to the resource message.
Tagging to Aliases
Previously, a concept of tag
existed. This concept was redundant with that of
an alias, and the terms were consolidated to reduce complexity in the AIPs.
Output only resource configuration
Although it was an option to have the revision take in the resource configuration as part of the create method, doing so would have allowed users to submit resource configuration for a revision that the resource was never in.
OUTPUT_ONLY
and requiring that a created revision represents the resource at
current point in time eliminates that issue.
History
Switching from a collection extension to a subcollection
In 2023-09, revisions are abstracted as a nested resource collection. Prior to
this, revisions are more like extension of an existing resource by using @
symbol. List and delete revisions were custom methods on the resource
collection. A single Get method was used to retrieve either the resource
revision, or the resource.
Its primary advantage was allowing a resource reference to seamlessly refer to a resource, or its revision.
It also had several disadvantages:
- List revisions is a custom method (:listRevisions) on the resource collection
- Delete revision is a custom method on the resource collection
- Not visible in API discovery doc
- Resource ID cannot use
@
The guidance was modified ultimately to enable revisions to behave like a resource, which reduces the users cognitive load and allows resource-oriented clients to easily list, get, create, and update revisions.
Using resource ID instead of tag
In the previous design, revisions had a separate identifer for a revision known
as a tag
, that would live in a revision.
Tags were effectively a shadow resource ID, requiring methods to create, get and filter revisions based on the value of the tag.
By consolidating the concept of a tag into the revision ID, the user no longer needs to be familiar with a second set of retrieval and identifier methods.
Changelog
- 2023-09-01: AIP was updated to be a sub-collection.
- 2021-04-27: Added guidance on returning the resource from Delete Revision.