Skip to content

Commit 8977b65

Browse files
author
Luke Sneeringer
authored
feat: AIP-164 – Soft delete (#38)
1 parent cfc42e7 commit 8977b65

4 files changed

Lines changed: 244 additions & 0 deletions

File tree

aip/general/0164/aip.md.j2

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Soft delete
2+
3+
There are several reasons why a client could desire soft delete and undelete
4+
functionality, but one over-arching reason stands out: recovery from mistakes.
5+
A service that supports undelete makes it possible for users to recover
6+
resources that were deleted by accident.
7+
8+
## Guidance
9+
10+
Services **may** support the ability to "undelete", to allow for situations
11+
where users mistakenly delete resources and need the ability to recover.
12+
13+
If a resource needs to support undelete, the `Delete` method **must** simply
14+
mark the resource as having been deleted, but not completely remove it from the
15+
system. If the method behaves this way, it **should** return `200 OK` with the
16+
updated resource instead of `204 No Content`.
17+
18+
Resources that support soft delete **should** have an `expire_time` field as
19+
described in AIP-148. Additionally, resources **should** include a `DELETED`
20+
state value if the resource includes a `state` field (AIP-216).
21+
22+
### Undelete
23+
24+
A resource that supports soft delete **should** provide an `Undelete` method:
25+
26+
{% tab proto %}
27+
28+
{% sample 'undelete.proto', 'rpc UndeleteBook', 'message UndeleteBookRequest' %}
29+
30+
- The HTTP method **must** be `POST`.
31+
- The `body` clause **must** be `"*"`.
32+
- The response message **must** be the resource itself. There is no
33+
`UndeleteBookResponse`.
34+
- The response **should** include the fully-populated resource unless it is
35+
infeasible to do so.
36+
- If the undelete RPC is [long-running](#long-running-undelete), the response
37+
message **must** be a `google.longrunning.Operation` which resolves to the
38+
resource itself.
39+
- A `name` field **must** be included in the request message; it **should** be
40+
called `name`.
41+
- The field **should** be [annotated as required][aip-203].
42+
- The field **should** identify the [resource type][aip-123] that it
43+
references.
44+
- The comment for the field **should** document the resource pattern.
45+
- The request message **must not** contain any other required fields, and
46+
**should not** contain other optional fields except those described in this
47+
or another AIP.
48+
49+
{% tab oas %}
50+
51+
{% sample 'undelete.oas.yaml', 'paths' %}
52+
53+
- The HTTP method **must** be `POST`.
54+
- The response message **must** be the resource itself.
55+
- The response **should** include the fully-populated resource unless it is
56+
infeasible to do so.
57+
- The operation **must not** require any other fields, and **should not**
58+
contain other optional query parameters except those described in this or
59+
another AIP.
60+
61+
{% endtabs %}
62+
63+
### Long-running undelete
64+
65+
Some resources take longer to undelete a resource than is reasonable for a
66+
regular API request. In this situation, the API **should** follow the
67+
long-running request pattern (AIP-151).
68+
69+
### List and Get
70+
71+
Soft-deleted resources **should not** be returned in `List` (AIP-132) responses
72+
by default (unless `bool show_deleted` is true).
73+
74+
A `Get` (AIP-131) request for a soft deleted resource **should** error with
75+
`410 Gone` unless `bool show_deleted` is true, in which case soft-deleted
76+
resources **must** return the resource.
77+
78+
Services that soft delete resources **may** choose a reasonable strategy for
79+
purging those resources, including automatic purging after a reasonable time
80+
(such as 30 days), allowing users to set an expiry time (AIP-214), or retaining
81+
the resources indefinitely. Regardless of what strategy is selected, the
82+
service **should** document when soft deleted resources will be completely
83+
removed.
84+
85+
### Declarative-friendly resources
86+
87+
A resource that is declarative-friendly (AIP-128) **should** support soft
88+
delete and undelete.
89+
90+
**Important:** There is an ambiguity in declarative tooling between "create"
91+
and "undelete". When given an alias which was previously deleted and a
92+
directive to make it exist, tooling usually does not know if the intent is to
93+
restore the previously-deleted resource, or create a new one with the same
94+
alias. Declarative tools **should** resolve this ambiguity in favor of creating
95+
a new resource: the only way to undelete is to explicitly use the undelete RPC
96+
(an imperative operation), and declarative tools **may** elect not to map
97+
anything to undelete at all.
98+
99+
Declarative-friendly resources **must** use long-running operations for both
100+
soft delete and undelete. The service **may** return an LRO that is already set
101+
to done if the request is effectively immediate.
102+
103+
Declarative-friendly resources **must** include `validate_only` (AIP-163) and
104+
`etag` (AIP-154) in their `Undelete` methods.
105+
106+
### Errors
107+
108+
If the user does not have permission to access the resource, regardless of
109+
whether or not it exists, the service **must** error with `403 Forbidden`.
110+
Permission **must** be checked prior to checking if the resource exists.
111+
112+
If the user does have proper permission, but the requested resource does not
113+
exist (either it was never created or already expunged), the service **must**
114+
error with `404 Not Found`.
115+
116+
If the user calling a soft `Delete` has proper permission, but the requested
117+
resource is already deleted, the service **must** succeed if `allow_missing` is
118+
`true`, and **should** error with `404 Not Found` if `allow_missing` is
119+
`false`.
120+
121+
If the user calling `Undelete` has proper permission, but the requested
122+
resource is not deleted, the service **must** error with `409 Conflict`.
123+
124+
## Further reading
125+
126+
- For the `Delete` standard method, see AIP-135.
127+
- For long-running operations, see AIP-151.
128+
- For resource freshness validation (`etag`), see AIP-154.
129+
- For change validation (`validate_only`), see AIP-163.

aip/general/0164/aip.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
id: 164
3+
state: approved
4+
created: 2020-10-06
5+
placement:
6+
category: design-patterns
7+
order: 95

aip/general/0164/undelete.oas.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
openapi: 3.0.3
3+
info:
4+
title: Library
5+
version: 1.0.0
6+
paths:
7+
/publishers/{publisherId}/books/{bookId}:undelete:
8+
post:
9+
operationId: undeleteBook
10+
description: Undelete a single book.
11+
responses:
12+
200:
13+
description: OK
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/Book'
18+
components:
19+
schema:
20+
Book:
21+
description: A representation of a single book.
22+
properties:
23+
name:
24+
type: string
25+
description: |
26+
The name of the book.
27+
Format: publishers/{publisher}/books/{book}
28+
isbn:
29+
type: string
30+
description: |
31+
The ISBN (International Standard Book Number) for this book.
32+
title:
33+
type: string
34+
description: The title of the book.
35+
authors:
36+
type: array
37+
items:
38+
type: string
39+
description: The author or authors of the book.
40+
rating:
41+
type: float
42+
description: The rating assigned to the book.

aip/general/0164/undelete.proto

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2021 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/client.proto";
19+
import "google/api/field_behavior.proto";
20+
import "google/api/resource.proto";
21+
22+
service Library {
23+
// Undelete a single book.
24+
rpc UndeleteBook(UndeleteBookRequest) returns (Book) {
25+
option (google.api.http) = {
26+
post: "/v1/{name=publishers/*/books/*}:undelete"
27+
body: "*"
28+
};
29+
option (google.api.method_signature) = "name";
30+
}
31+
}
32+
33+
// Request message to undelete a single book.
34+
message UndeleteBookRequest {
35+
// The name of the book to undelete.
36+
// The book must exist and currently be deleted (but not expunged).
37+
string name = 1 [
38+
(google.api.field_behavior) = REQUIRED,
39+
(google.api.resource_reference) = {
40+
type: "library.googleapis.com/Book"
41+
}];
42+
}
43+
44+
// A representation of a single book.
45+
message Book {
46+
option (google.api.resource) = {
47+
type: "library.googleapis.com/Book"
48+
pattern: "publishers/{publisher}/books/{book}"
49+
};
50+
51+
// The name of the book.
52+
// Format: publishers/{publisher}/books/{book}
53+
string name = 1;
54+
55+
// The ISBN (International Standard Book Number) for this book.
56+
string isbn = 2;
57+
58+
// The title of the book.
59+
string title = 3;
60+
61+
// The author or authors of the book.
62+
repeated string authors = 4;
63+
64+
// The rating assigned to the book.
65+
float rating = 5;
66+
}

0 commit comments

Comments
 (0)