Skip to content

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.

Guidance

APIs may expose a revision history for a resource. Examples of when it is useful include:

  • When it is valuable to expose older revisions 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 or descend from different revisions of a resource.
  • There is a need to represent the change of a resource over time.

APIs implementing resources with revisions must model resource revisions as a subresource of the resource.

message BookRevision {
// The path of the book revision.
string path = 1;
// The snapshot of the book
Book resource = 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 aliases = 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 resource.
    • The value of resource must be the original resource 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 aliases, 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 creating a new revision. Specifically examples of strategies include:

  • Creating a revision when there is a change to the resource
  • Creating a revision when important system state changes
  • Creating a revision when specifically requested

APIs may use any of these strategies or any other strategy. APIs must document their revision creation strategy.

Resources that support revisions should always have at least one revision.

Resource names for revisions

When referring to the revisions of a resource, the subcollection name must be revisions. Resource revisions have paths with the format {resource_path}/revisions/{resource_revision}. For example:

publishers/123/books/les-miserables/revisions/c7cfa2a8

The resource must be named {resource_singular}Revision (for example, BookRevision).

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_id}/books/{book_id}/revisions/{revision_id}

If specific IDs are reserved, services must document those IDs.

  • If a latest ID exists, it must represent the most recently created revision. The content of publishers/{publisher}/books/{book}/revisions/latest and publishers/{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(AliasBookRevisionRequest) returns (Book) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*/revisions/*}:alias"
body: "*"
};
}
message AliasBookRevisionRequest {
string path = 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 = 2 [(google.api.field_behavior) = REQUIRED];
// If false, this request will fail when the alias already
// exists.
bool overwrite = 3 [(google.api.field_behavior) = OPTIONAL];
}
  • The request message must have a path field:
  • The request message must have a alias field:
    • The field must be [annotated as required][aip-203].
  • If the user calls the method with an existing alias with overwrite set to true, the request must succeed and the alias will be updated to refer to the provided revision. If overwrite is false, the request must fail with an error code ALREADY_EXISTS (HTTP 409).

Rollback

A common use case for a resource with a revision history is the ability to roll back to a given revision. APIs that support this behavior should do so with a Rollback custom method:

rpc RollbackBook(RollbackBookRequest) returns (BookRevision) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*/revisions/*}:rollback"
body: "*"
};
}
message RollbackBookRequest {
// The revision that the book should be rolled back to.
string path = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {
type: "library.googleapis.com/BookRevision"
}];
}
  • The method must use the POST HTTP verb.
  • The method should return a resource revision.
  • The request message must have a path field, referring to the resource revision whose configuration the resource should be rolled back to.
    • The field must be [annotated as required][aip-203].
    • The field must identify the [resource type][aip-123] that it references.

Child resources

Resources with a revision history may have child resources. If they do, they should be a subset of the descendants of the original resource, and a given revision’s descendants must be a subset of the descendants of the resource at the time the revision was created.

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 should be ordered in reverse chronological order. APIs may support the order_by field to override the default behavior.
  • If the revision supports aliasing, a delete method with the resource path 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

For the name “revision”

There was significant debate about what to call this pattern, with the following as proposed options:

  • snapshots
  • revisions
  • versions

Among those, revision was chosen because:

  • The term “version” is often used in multiple different contexts (e.g. API version), and using that noun here may result in confusion during conversations where it could mean one or the other.
  • The term “snapshot” is also used for snapshots of datastores, which may not follow this pattern.
  • The term “revision” does not have many conflicts with terms when describing an API or resource.

History

Switching from a collection extension to a subcollection

In aip.dev prior to 2023-09, revisions were more like extensions 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.
  • Delete revision is a custom method on the resource.
  • Not visible in API discovery documenation.
  • Resource IDs cannot use the @ symbol.

The guidance was modified ultimately to enable revisions to behave like a resource, which reduces users’ cognitive load and allows resource-oriented clients to easily list, get, create, update, and delete revisions.

Changelog

  • 2024-08-09: Imported from aip.dev.