AIP-136
Custom operations
Services use custom operations to provide a means to express arbitrary actions that are difficult to model using only the standard operations. Custom operations are important because they provide a means for an API's vocabulary to adhere to user intent.
Guidance
Custom operations should only be used for functionality that can not be easily expressed via standard operations; prefer standard operations if possible, due to their consistent semantics. (Of course, this only applies if the functionality in question actually conforms to the normal semantics; it is not a good idea to contort things to endeavor to make the standard operations "sort of work".)
While custom operations vary widely in how they are designed, many principles apply consistently:
// Archives the given book.
rpc ArchiveBook(ArchiveBookRequest) returns (ArchiveBookResponse) {
option (google.api.http) = {
post: "/v1/{name=publishers/*/books/*}:archive"
body: "*"
};
}
- The name of the RPC should be a verb followed by a noun.
- The name must not contain prepositions ("for", "with", etc.).
- The HTTP method for custom operations should usually be
POST, unless the custom method maps more strongly to another HTTP verb.- Custom operations that serve as an alternative to get or list operations
(such as
Search) should useGET. These operations must be idempotent and have no state changes or side effects (they should be safe as defined in RFC 7231). - Custom operations should not use
PATCHorDELETE.
- Custom operations that serve as an alternative to get or list operations
(such as
- The HTTP URI must use a
:character followed by the custom verb (:archivein the above example), and the verb in the URI must match the verb in the name of the RPC.- If word separation is required,
camelCasemust be used.
- If word separation is required,
- The
bodyclause in thegoogle.api.httpannotation should be"*".- However, if using
GETorDELETE, thebodyclause must be absent.
- However, if using
- Custom operations should usually take a request message matching the RPC
name, with a -
Requestsuffix. - Custom operations should usually return a response message matching the
RPC name, with a -
Responsesuffix.- When operating on a specific resource, a custom method may return the resource itself.
/publishers/{publisherId}/books/{bookId}:archive:
post:
operationId: archiveBook
description: Archives the given book.
requestBody:
description:
content:
application/json:
schema:
description: Request structure to archive a book.
properties:
mime_type:
type: string
description: |
The target format for the archived book.
Must be "application/pdf", "application/rtf", or
"application/epub+zip"
required: true
enum:
- application/pdf
- application/rtf
- application/epub+zip
responses:
200:
description: OK
content:
application/json:
schema:
description: Response structure for the archiveBook operation.
properties:
uri:
type: string
description: The location of the archived book.
- The
operationIdshould be a verb followed by a noun.- The
operationIdmust not contain prepositions ("for", "with", etc.).
- The
- The HTTP method for custom operations should usually be
POST, unless the custom method maps more strongly to another HTTP verb.- Custom operations that serve as an alternative to get or list operations
(such as
Search) should useGET, and require no request body. These operations must be idempotent and have no state changes or side effects (they should be safe as defined in RFC 7231). - Custom operations should not use
PATCHorDELETE.
- Custom operations that serve as an alternative to get or list operations
(such as
- The HTTP URI must use a
:character followed by the custom verb (:archivein the above example), and the verb in the URI must match the verb in theoperationId.- If word separation is required,
camelCasemust be used.
- If word separation is required,
Note: The pattern above shows a custom method that operates on a specific resource. Custom operations can be associated with resources, collections, or services.
Collection-based custom operations
While most custom operations operate on a single resource, some custom operations may operate on a collection instead:
// Sorts the books from this publisher.
rpc SortBooks(SortBooksRequest) returns (SortBooksResponse) {
option (google.api.http) = {
post: "/v1/{publisher=publishers/*}/books:sort"
body: "*"
};
}
/publishers/{publisherId}/books:sort:
post:
operationId: sortBooks
description: Sorts the books from this publisher.
requestBody:
description: Request structure to sort a collection of books.
properties:
field:
type: string
description: |
The property of the book to sort by.
If not provided, "title" is used.
responses:
200:
description: OK
- If the collection has a parent, the field name in the request message
should be the target resource's singular noun (
publisherin the above example). If word separators are necessary,snake_casemust be used. - The collection key (
booksin the above example) must be literal.
Stateless operations
Some custom operations are not attached to resources at all. These operations are generally stateless: they accept a request and return a response, and have no permanent effect on data within the API.
// Translates the provided text from one language to another.
rpc TranslateText(TranslateTextRequest) returns (TranslateTextResponse) {
option (google.api.http) = {
post: "/v1/{project=projects/*}:translateText"
body: "*"
};
}
/projects/{projectId}:translateText:
post:
operationId: translateText
description: Translates the provided text from one language to another.
requestBody:
description:
content:
application/json:
schema:
description: Request structure to translate text.
properties:
contents:
type: array
items:
type: string
description: The contents of the input, as a string.
source_language_code:
type: string
description: |
The BCP-47 language code of the input text (e.g. "en-US").
If the source language is not specified, the service will
attempt to infer it.
target_language_code:
type: string
description: |
The BCP-47 language code of the output text (e.g. "en-US").
responses:
200:
description: OK
content:
application/json:
schema:
description: |
Response structure for the translateText operation.
properties:
translated_text:
type: string
description: |
Text translated into the target language.
detected_language_code:
type: string
description: |
The BCP-47 language code of source text in the initial
request, if it was detected automatically.
- If the method runs in a particular scope (such as a project, as in the above
example), the field name in the request message should be the name of the
scope resource. If word separators are necessary,
snake_casemust be used. - The URI should place both the verb and noun after the
:separator (avoid a "faux collection key" in the URI in this case, as there is no collection). For example,:translateTextis preferable totext:translate. - Stateless operations must use
POSTif they involve billing.
Declarative-friendly resources
Declarative-friendly resources usually should not employ custom operations (except specific declarative-friendly custom operations discussed in other AIPs), because declarative-friendly tools are unable to automatically determine what to do with them.
An exception to this is for rarely-used, fundamentally imperative operations,
such as a Move, Rename, or Restart operation, for which there would not
be an expectation of declarative support.
View on GitHub