|
| 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 --> |
0 commit comments