Skip to content

Commit 031fbe8

Browse files
authored
Initial pass at AEP-135 (Standard Methods: Delete) (#94)
* Copy Google AIP * Initial pass at AEP-135 * Address PR review comments * Move strong consistency guidance to common section
1 parent 5f50abe commit 031fbe8

6 files changed

Lines changed: 357 additions & 7 deletions

File tree

aep/general/0135/aep.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

aep/general/0135/aep.md.j2

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
# Delete
2+
3+
In REST APIs, it is customary to make a `DELETE` request to a resource's URI
4+
(for example, `/v1/publishers/{publisher}/books/{book}`) in order to delete
5+
that resource.
6+
7+
Resource-oriented design ([AEP-121][aep-121]) honors this pattern through the
8+
`Delete` method. This method accepts the URI representing that resource and
9+
usually returns an empty response.
10+
11+
## Guidance
12+
13+
APIs **should** generally provide a delete method for resources unless it is
14+
not valuable for users to do so.
15+
16+
The Delete method **should** succeed if and only if a resource was present and
17+
was successfully deleted. If the resource did not exist, the method **should**
18+
send a `404 Not found` (`NOT_FOUND`) error.
19+
20+
If the API is operating on the [Management Plane][], the method should have
21+
[strong consistency][]: the completion of a delete method **must** mean that
22+
the existence of the resource has reached a steady-state and reading resource
23+
state returns a consistent `404 Not found`(`NOT_FOUND`) response.
24+
25+
### Requests
26+
27+
{% tab oas -%}
28+
29+
Delete methods are specified using the following pattern:
30+
31+
{% sample 'delete.oas.yaml', 'paths' %}
32+
33+
- The HTTP verb **must** be `DELETE`.
34+
- There **must not** be a request body in API description.
35+
- If a delete request contains a body, the body **must** be ignored, and **must
36+
not** cause an error (this is required by [RFC 9110][])
37+
- The request **must not** require any fields in the query string. The request
38+
**should not** include optional fields in the query string unless described
39+
in another AEP.
40+
- Delete methods **should** return `204 No Content` with no response body, or
41+
`202 Accepted` with a representation of the operation in the response body
42+
if the delete is [long-running](#long-running-delete).
43+
44+
{% tab proto -%}
45+
46+
Delete methods are specified using the following pattern:
47+
48+
```proto
49+
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {
50+
option (google.api.http) = {
51+
delete: "/v1/{name=publishers/*/books/*}"
52+
};
53+
option (google.api.method_signature) = "name";
54+
}
55+
```
56+
57+
- The RPC's name **must** begin with the word `Delete`. The remainder of the
58+
RPC name **should** be the singular form of the resource's message name.
59+
- The request message **must** match the RPC name, with a `Request` suffix.
60+
- The response message **should** be `google.protobuf.Empty`.
61+
- If the delete RPC is [long-running](#long-running-delete), the response
62+
message **must** be a `library.longrunning.Operation` which resolves to the
63+
correct response.
64+
- The request message field receiving the resource name **should** map to the
65+
URI path.
66+
- This field **should** be called `name`.
67+
- The `name` field **should** be the only variable in the URI path. All
68+
remaining parameters **should** map to URI query parameters.
69+
- There **must not** be a `body` key in the `google.api.http` annotation.
70+
- There **should** be exactly one `google.api.method_signature` annotation,
71+
with a value of `"name"`. If an etag or force field are used, they **may** be
72+
included in the signature.
73+
74+
Delete methods implement a common request pattern:
75+
76+
```proto
77+
message DeleteBookRequest {
78+
// The path of the book to delete.
79+
// Format: publishers/{publisher}/books/{book}
80+
string path = 1 [
81+
(google.api.field_behavior) = REQUIRED,
82+
(google.api.resource_reference) = {
83+
type: "library.example.com/Book"
84+
}];
85+
}
86+
```
87+
88+
- A `path` field **must** be included. It **should** be called `path`.
89+
- The field **should** be [annotated as required][aep-203].
90+
- The field **must** identify the [resource type][aep-123] that it
91+
references.
92+
- The comment for the field **should** document the resource pattern.
93+
- The request message **must not** contain any other required fields, and
94+
**should not** contain other optional fields except those described in this
95+
or another AEP.
96+
97+
{% endtabs %}
98+
99+
### Soft delete
100+
101+
**Note:** This material was moved into its own document to provide a more
102+
comprehensive treatment: [AEP-164][aep-164].
103+
104+
### Long-running delete
105+
106+
Some resources take longer to delete than is reasonable for a regular API
107+
request. In this situation, the API **should** use a long-running operation
108+
instead: [AEP-151][aep-151].
109+
110+
{% tab oas -%}
111+
112+
{% sample 'long_running_delete.oas.yaml', 'paths' %}
113+
114+
- The response status code should be `202 Accepted` if the request was accepted
115+
for later processing. When the request is processed it could still fail.
116+
- The `response` field of the response body **must** be an empty object to be
117+
consistent with the appropriate return type if the method was not
118+
long-running.
119+
- Both the `response_type` and `metadata_type` fields **must** be specified.
120+
121+
{% tab proto -%}
122+
123+
```proto
124+
rpc DeleteBook(DeleteBookRequest) returns (library.longrunning.Operation) {
125+
option (google.api.http) = {
126+
delete: "/v1/{name=publishers/*/books/*}"
127+
};
128+
option (library.longrunning.operation_info) = {
129+
response_type: "google.protobuf.Empty"
130+
metadata_type: "OperationMetadata"
131+
};
132+
}
133+
```
134+
135+
- The `response` field of the response **must** be `google.protobuf.Empty` to
136+
be consistent with the appropriate return type if the method was not
137+
long-running.
138+
- Both the `response_type` and `metadata_type` fields **must** be specified
139+
(even if they are `google.protobuf.Empty`).
140+
141+
{% endtabs %}
142+
143+
### Cascading delete
144+
145+
Sometimes, it may be necessary for users to be able to delete a resource as
146+
well as all applicable child resources. However, since deletion is usually
147+
permanent, it is also important that users not do so accidentally, as
148+
reconstructing wiped-out child resources may be quite difficult.
149+
150+
If an API allows deletion of a resource that may have child resources, the API
151+
**must** provide a `bool force` field on the request, which the user sets to
152+
explicitly opt in to a cascading delete.
153+
154+
{% tab oas -%}
155+
156+
{% sample 'cascading_delete.oas.yaml', 'paths' %}
157+
158+
The API **must** fail with a `409 Conflict` error if the `force` field is
159+
`false` (or unset) and child resources are present.
160+
161+
{% tab proto -%}
162+
163+
```proto
164+
message DeletePublisherRequest {
165+
// The name of the publisher to delete.
166+
// Format: publishers/{publisher}
167+
string name = 1 [
168+
(google.api.field_behavior) = REQUIRED,
169+
(google.api.resource_reference) = {
170+
type: "library.example.com/Publisher"
171+
}];
172+
173+
// If set to true, any books from this publisher will also be deleted.
174+
// (Otherwise, the request will only work if the publisher has no books.)
175+
bool force = 2;
176+
}
177+
```
178+
179+
The API **must** fail with a `FAILED_PRECONDITION` error if the `force` field
180+
is `false` (or unset) and child resources are present.
181+
182+
{% endtabs %}
183+
184+
### Errors
185+
186+
If the user does not have permission to access the resource, regardless of
187+
whether or not it exists, the service **must** error with `403 Forbidden`
188+
(`PERMISSION_DENIED`). Permission **must** be checked prior to checking if the
189+
resource exists.
190+
191+
If the user does have proper permission, but the requested resource does not
192+
exist, the service **must** error with `404 Not found` (`NOT_FOUND`).
193+
194+
## Further reading
195+
196+
- For soft delete and undelete, see [AEP-164][aep-164].
197+
- For bulk deleting large numbers of resources based on a filter, see
198+
[AEP-165][aep-165].
199+
200+
## Changelog
201+
202+
- **2024-02-11**: From from https://google.aip.dev/135
203+
204+
<!-- prettier-ignore-start -->
205+
[aep-123]: ./0123.md
206+
[aep-128]: ./0128.md
207+
[aep-131]: ./0131.md
208+
[aep-132]: ./0132.md
209+
[aep-136]: ./0136.md
210+
[aep-154]: ./0154.md
211+
[aep-164]: ./0164.md
212+
[aep-165]: ./0165.md
213+
[aep-203]: ./0203.md
214+
[aep-214]: ./0214.md
215+
[aep-216]: ./0216.md
216+
[management plane]: ./0111.md#management-plane
217+
[strong consistency]: ./0121.md#strong-consistency
218+
[etag]: ./0134.md#etags
219+
[RFC 9110]: https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
220+
<!-- prettier-ignore-end -->

aep/general/0135/aep.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
id: 135
3-
state: reviewing
3+
state: approved
44
slug: delete
5-
created: 2023-01-22
5+
created: 2024-02-11
66
placement:
77
category: standard-methods
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Library
4+
version: 1.0.0
5+
paths:
6+
/publishers/{publisherId}:
7+
parameters:
8+
- $ref: '#/components/parameters/PublisherId'
9+
delete:
10+
operationId: deletePublisher
11+
description: Delete a publisher.
12+
parameters:
13+
- in: query
14+
name: force
15+
description: |
16+
If set to true, any books from this publisher will also be deleted.
17+
(Otherwise, the request will only work if the publisher has no books.)
18+
schema:
19+
type: boolean
20+
default: false
21+
responses:
22+
'204':
23+
description: Publisher was deleted
24+
'404':
25+
description: Not found
26+
'409':
27+
description: |
28+
Delete request failed because publisher has child resource and the
29+
`force` parameter was set or defaulted to `false`.
30+
components:
31+
parameters:
32+
PublisherId:
33+
name: publisherId
34+
in: path
35+
description: The id of the book publisher.
36+
required: true
37+
schema:
38+
type: string

aep/general/0135/delete.oas.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
openapi: 3.0.3
3+
info:
4+
title: Library
5+
version: 1.0.0
6+
paths:
7+
/publishers/{publisherId}/books/{bookId}:
8+
parameters:
9+
- $ref: '#/components/parameters/PublisherId'
10+
- $ref: '#/components/parameters/BookId'
11+
delete:
12+
operationId: deleteBook
13+
description: Delete a single book.
14+
responses:
15+
'204':
16+
description: Book was deleted
17+
'404':
18+
description: Not found
19+
components:
20+
parameters:
21+
PublisherId:
22+
name: publisherId
23+
in: path
24+
description: The id of the book publisher.
25+
required: true
26+
schema:
27+
type: string
28+
BookId:
29+
name: bookId
30+
in: path
31+
description: The id of the book.
32+
required: true
33+
schema:
34+
type: string
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
openapi: 3.0.3
2+
info:
3+
title: Library
4+
version: 1.0.0
5+
paths:
6+
/publishers/{publisherId}/books/{bookId}:
7+
parameters:
8+
- $ref: '#/components/parameters/PublisherId'
9+
- $ref: '#/components/parameters/BookId'
10+
delete:
11+
operationId: deleteBook
12+
description: Delete a single book.
13+
responses:
14+
'202':
15+
description: Accepted
16+
content:
17+
application/json:
18+
schema:
19+
$ref: '#/components/schemas/DeleteBookOperation'
20+
components:
21+
parameters:
22+
PublisherId:
23+
name: publisherId
24+
in: path
25+
description: The id of the book publisher.
26+
required: true
27+
schema:
28+
type: string
29+
BookId:
30+
name: bookId
31+
in: path
32+
description: The id of the book.
33+
required: true
34+
schema:
35+
type: string
36+
schemas:
37+
DeleteBookOperation:
38+
description: The status of the deleteBook operation.
39+
properties:
40+
response:
41+
type: object
42+
description: Empty object.
43+
metadata:
44+
type: object
45+
properties:
46+
startTime:
47+
type: string
48+
format: date-time
49+
description: The time the operation started.
50+
progressPercent:
51+
type: integer
52+
format: int32
53+
description: The current progress, expressed as an integer.
54+
state:
55+
type: string
56+
description: The current state of the operation.
57+
enum:
58+
- STATE_UNSPECIFIED
59+
- RUNNING
60+
- SUCCEEDED
61+
- CANCELLING
62+
- CANCELLED
63+
- FAILED

0 commit comments

Comments
 (0)