This AIP is currently a draft. This means that it is being actively debated and discussed, and it may change in non-trivial ways.

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 of snapshot 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 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(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 request message must have a alias_id field:
  • 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.

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.