Skip to content

Commit edbc2cc

Browse files
author
Luke Sneeringer
authored
feat: AIP-151 – Long-running operations (#17)
1 parent 5879f0e commit edbc2cc

4 files changed

Lines changed: 346 additions & 0 deletions

File tree

aip/general/0151/aip.md.j2

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
# Long-running requests
2+
3+
Occasionally, a service may need to expose an operation that takes a
4+
significant amount of time to complete. In these situations, it is often a poor
5+
user experience to simply block while the task runs; rather, it is better to
6+
return some kind of promise to the user, and allow the user to check back in
7+
later.
8+
9+
The long-running request pattern is roughly analogous to a [Future][] in Python
10+
or Java, or a [Node.js Promise][]. Essentially, the user is given a token that
11+
can be used to track progress and retrieve the result.
12+
13+
## Guidance
14+
15+
Operations that might take a significant amount of time to complete **should**
16+
return a `202 Accepted` response along with an identifier that can be used to
17+
track the status of the request and ultimately retrieve the result.
18+
19+
Any single operation defined in an API surface **must** either _always_ return
20+
`202 Accepted` along with a request identifier, or _never_ do so. A service
21+
**must not** return a `200 OK` response with the result if it is "fast enough",
22+
and `202 Accepted` if it is not fast enough, because such behavior adds
23+
significant burdens for clients.
24+
25+
**Note:** User expectations can vary on what is considered "a significant
26+
amount of time" depending on what work is being done. A good rule of thumb is
27+
10 seconds.
28+
29+
### Status monitor representation
30+
31+
The response to a long-running request **should** be a "status monitor" having
32+
the following common format:
33+
34+
```typescript
35+
interface StatusMonitor {
36+
// The identifier for this status monitor.
37+
id: string;
38+
39+
// Whether the request is done.
40+
done: boolean;
41+
42+
// The result of the request.
43+
// Only populated if the request is done and was successful.
44+
response: any;
45+
46+
// The error that arose from the request.
47+
// Only populated if the request is done and was unsuccessful.
48+
error: Error;
49+
50+
// Metadata associated with the request.
51+
// Populated throughout the life of the request, including after
52+
// it completes.
53+
metadata: any;
54+
}
55+
```
56+
57+
- If the `done` field is `true`, then one and exactly one of the `response` and
58+
`error` fields **must** be populated.
59+
- If the `done` field is `false`, then the `response` and `error` fields
60+
**must not** be populated.
61+
- The `response` and `metadata` fields **may** be any type that the service
62+
determines to be appropriate, but **must** always be the same type for any
63+
particular operation.
64+
- The `response` and `metadata` types **should** be defined in the same API
65+
surface as the operation itself.
66+
- The `response` and `metadata` types that need no data **should** use a
67+
custom-defined empty struct rather than a common void or empty type, to
68+
permit future extensibility.
69+
70+
### Querying a status monitor
71+
72+
The service **must** provide an endpoint to query the status of the operation,
73+
which **must** accept the operation identifier and **should not** include other
74+
parameters:
75+
76+
```http
77+
GET /v1/statusMonitors/{status_monitor} HTTP/2
78+
Host: library.googleapis.com
79+
Accept: application/json
80+
```
81+
82+
The endpoint **must** return a `StatusMonitor` as described above.
83+
84+
### Standard methods
85+
86+
APIs **may** return an `StatusMonitor` from the [`Create`][aip-133],
87+
[`Update`][aip-134], or [`Delete`][aip-135] standard methods if appropriate. In
88+
this case, the `response` field **must** be the standard and expected response
89+
type for that standard method.
90+
91+
When creating or deleting a resource with a long-running request, the resource
92+
**should** be included in [`List`][aip-132] and [`Get`][aip-131] calls;
93+
however, the resource **should** indicate that it is not usable, generally with
94+
a [state enum][aip-216].
95+
96+
### Parallel requests
97+
98+
A resource **may** accept multiple requests that will work on it in parallel,
99+
but is not obligated to do so:
100+
101+
- Resources that accept multiple parallel requests **may** place them in a
102+
queue rather than work on the requests simultaneously.
103+
- Resource that does not permit multiple requests in parallel (denying any new
104+
request until the one that is in progress finishes) **must** return
105+
`409 Conflict` if a user attempts a parallel request, and include an error
106+
message explaining the situation.
107+
108+
### Expiration
109+
110+
APIs **may** allow their status monitor resources to expire after sufficient
111+
time has elapsed after the request completed.
112+
113+
**Note:** A good rule of thumb for status monitor expiry is 30 days.
114+
115+
### Errors
116+
117+
Errors that prevent a long-running request from _starting_ **must** return an
118+
error response (AIP-193), similar to any other method.
119+
120+
Errors that occur over the course of a request **may** be placed in the
121+
metadata message. The errors themselves **must** still be represented with a
122+
canonical error object.
123+
124+
## Interface Definitions
125+
126+
{% tab proto %}
127+
128+
When using protocol buffers, the well-known type `google.longrunning.Operation`
129+
is used.
130+
131+
**Note:** For historical reasons, Google uses the term `Operation` to represent
132+
what this document describes as a `StatusMonitor`.
133+
134+
{% sample 'lro.proto', 'rpc WriteBook' %}
135+
136+
- The response type **must** be `google.longrunning.Operation`. The `Operation`
137+
proto definition **must not** be copied into individual APIs.
138+
- The response **must not** be a streaming response.
139+
- The method **must** include a `google.longrunning.operation_info` annotation,
140+
which **must** define both response and metadata types.
141+
- The response and metadata types **must** be defined in the file where the
142+
RPC appears, or a file imported by that file.
143+
- If the response and metadata types are defined in another package, the
144+
fully-qualified message name **must** be used.
145+
- The response type **should not** be `google.protobuf.Empty` (except for
146+
[`Delete`][aip-135] methods), unless it is certain that response data will
147+
_never_ be needed. If response data might be added in the future, define an
148+
empty message for the RPC response and use that.
149+
- The metadata type is used to provide information such as progress, partial
150+
failures, and similar information on each `GetOperation` call. The metadata
151+
type **should not** be `google.protobuf.Empty`, unless it is certain that
152+
metadata will _never_ be needed. If metadata might be added in the future,
153+
define an empty message for the RPC metadata and use that.
154+
- APIs with messages that return `Operation` **must** implement the
155+
[`Operations`][lro] service. Individual APIs **must not** define their own
156+
interfaces for long-running operations to avoid inconsistency.
157+
158+
{% tab oas %}
159+
160+
{% sample 'lro.oas.yaml', 'paths' %}
161+
162+
- `202` **must** be the only success status code defined.
163+
- The `202` response **must** define an `application/json` response body and no other
164+
response content types.
165+
- The response body schema **must** be an object with `name`, `done`, and `result`
166+
properties as described above for a StatusMonitor
167+
- The response body schema **may** contain an object property named `metadata` to
168+
hold service-specific metadata associated with the operation, for example progress
169+
information and common metadata such as create time. The service **should** define
170+
the contents of the `metadata` object in a separate schema, which **should** specify
171+
`additionalProperties: true` to allow for future extensibility.
172+
- The `response` property **must** be a schema that defines the success
173+
response for the operation. For an operation that typically gives a `204 No Content`
174+
response, such as a `Delete`, `response` should be defined as an empty object schema.
175+
For a standard `Get/Create/Update` operation, `response` should be a representation
176+
of the resource.
177+
- If a service has any long running operations, the service **must** define an
178+
`StatusMonitor` resource with a `list` operation to retrieve a potentially filtered
179+
list of status monitors and a `get` operation to retrieve a specific status monitor
180+
by its `name`.
181+
182+
{% endtabs %}
183+
184+
<!-- prettier-ignore-start -->
185+
[google.rpc.Status]: https://github.com/googleapis/api-common-protos/blob/master/google/rpc/S.proto
186+
[lro]: https://github.com/googleapis/api-common-protos/blob/master/google/longrunning/operations.proto
187+
[node.js promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
188+
[future]: https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future
189+
<!-- prettier-ignore-end -->

aip/general/0151/aip.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
id: 151
3+
state: reviewing
4+
created: 2019-07-25
5+
placement:
6+
category: operations
7+
order: 70

aip/general/0151/lro.oas.yaml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Library
4+
version: 1.0.0
5+
paths:
6+
/v1/resources:
7+
post:
8+
operationId: write_book
9+
description: Write a book.
10+
responses:
11+
202:
12+
description: OK
13+
content:
14+
application/json:
15+
schema:
16+
$ref: '#/components/schemas/WriteBookStatus'
17+
18+
components:
19+
schemas:
20+
StatusMonitor:
21+
description: The status of a long running operation.
22+
properties:
23+
name:
24+
type: string
25+
description: The server-assigned name, which is only unique within the same service that originally returns it.
26+
done:
27+
type: boolean
28+
description: >-
29+
If the value is false, it means the operation is still in progress. If true, the operation is completed,
30+
and either response or error is available.
31+
error:
32+
$ref: '#/components/schemas/Error'
33+
required:
34+
- name
35+
- done
36+
37+
WriteBookStatus:
38+
description: The status of a write_book operation.
39+
allOf:
40+
- $ref: '#/components/schemas/StatusMonitor'
41+
- type: object
42+
properties:
43+
response:
44+
type: string
45+
description: The text that was written.
46+
metadata:
47+
type: object
48+
properties:
49+
start_time:
50+
type: string
51+
format: date-time
52+
description: The time the operation started.
53+
progress_percent:
54+
type: integer
55+
format: int32
56+
description: The current progress, expressed as an integer.
57+
state:
58+
type: string
59+
description: The current state of the operation.
60+
enum:
61+
- STATE_UNSPECIFIED
62+
- RUNNING
63+
- CANCELLING
64+
- CANCELLED
65+
- FAILED

aip/general/0151/lro.proto

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
import "google/api/annotations.proto";
18+
import "google/api/resource.proto";
19+
import "google/longrunning/operations.proto";
20+
import "google/protobuf/timestamp.proto";
21+
22+
service Library {
23+
// Write a book.
24+
rpc WriteBook(WriteBookRequest) returns (google.longrunning.Operation) {
25+
option (google.api.http) = {
26+
post: "/v1/{parent=publishers/*}/books:write"
27+
body: "*"
28+
};
29+
option (google.longrunning.operation_info) = {
30+
response_type: "WriteBookResponse"
31+
metadata_type: "WriteBookMetadata"
32+
};
33+
}
34+
}
35+
36+
// The request message for the WriteBook endpoint.
37+
// Unlike the response, this message is accepted directly.
38+
message WriteBookRequest {
39+
// The publisher for which the book is to be written.
40+
string parent = 1 [(google.api.resource_reference) = {
41+
child_type: "library.googleapis.com/Book"
42+
}];
43+
44+
// The title of the new book.
45+
string title = 2;
46+
}
47+
48+
// The response message for the WriteBook endpoint.
49+
// When WriteBook is called, it will not send this response directly; instead,
50+
// it sends a long-running operation; the operation will contain this in
51+
// the `response` field once it completes.
52+
message WriteBookResponse {
53+
// The text that was written.
54+
string text = 1;
55+
}
56+
57+
// The metadata message for the WriteBook endpoint.
58+
// This is populated in the `metadata` field for all WriteBook LROs.
59+
message WriteBookMetadata {
60+
// The time the operation started.
61+
google.protobuf.Timestamp start_time = 1;
62+
63+
// The current progress, expressed as an integer: [0, 100].
64+
int32 progress_percent = 2;
65+
66+
enum State {
67+
STATE_UNSPECIFIED = 0;
68+
69+
// The operation is running.
70+
RUNNING = 1;
71+
72+
// The operation is still running, but cancellation has been requested
73+
// and accepted, and is in progress.
74+
CANCELLING = 2;
75+
76+
// The operation was cancelled.
77+
CANCELLED = 3;
78+
79+
// The operation failed.
80+
FAILED = 4;
81+
}
82+
83+
// The current state of the operation.
84+
State state = 3;
85+
}

0 commit comments

Comments
 (0)