Skip to content

Commit b2a7ced

Browse files
author
Dan Kershaw [MSFT]
committed
Addressed Mike Pizzo's comments
1 parent dc6d28e commit b2a7ced

File tree

2 files changed

+61
-27
lines changed

2 files changed

+61
-27
lines changed

graph/GuidelinesGraph.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ If possible, APIs SHOULD use resource-based designs with standard HTTP methods r
251251
| Microsoft Graph rules for modeling behavior |
252252
|------------------------------------------------------------------|
253253
| :heavy_check_mark: **MUST** use POST to create new entities in insertable entity sets or collections.<BR>This approach requires the service to produce a system-generated key, or for a caller to provide a key in the request payload. |
254-
| :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.<BR>This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key. |
254+
| :ballot_box_with_check: **SHOULD** additionally use PATCH to create new entities in insertable entity sets or collections.<BR>This [Upsert](./patterns/upsert.md) approach requires the caller to provide a key in the request URL. |
255255
| :heavy_check_mark: **MUST** use PATCH to edit updatable resources. |
256256
| :heavy_check_mark: **MUST** use DELETE to delete deletable resources. |
257257
| :heavy_check_mark: **MUST** use GET for listing and reading resources. |

graph/patterns/upsert.md

Lines changed: 60 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,18 @@ Additionally, IaC code scripts or templates usually employ client-provided names
2121

2222
The solution is to use an `Upsert` pattern, to solve for the non-idempotent creation and client-provided naming problems.
2323

24-
* For IaC scenarios, resources must use `Upsert` semantics with a client-provided key:
25-
* Use `PATCH` with a client-provided key:
26-
* If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key.
27-
* If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation.
28-
* For a non-existent resource (specified by the client-provided key) the service must handle this as a "create". As part of creation, the service must still generate the primary key value, if appropriate.
24+
* `Upsert` uses `PATCH` with a client-provided key in the URL:
25+
* If there is a natural client-provided key that can serve as the primary key, then the service should support `Upsert` with that key.
26+
* If the primary key is service-generated, the client-provided key should use an [alternate key](./alternate-key.md) to support idempotent creation.
27+
* For a non-existent resource (specified by the client-provided key) the service must handle this as a "create" (aka insert). As part of creation, the service must still generate the primary key value, if appropriate.
2928
* For an existing resource (specified by the client-provided key) the service must handle this as an "update".
30-
* Any new alternate key, used for IaC scenarios, should be called `uniqueName`, if there isn't already a more natural existing property that could be used as an alternate key.
31-
* NOTE: the service must also support `GET` using the alternate key pattern.
29+
* If using an alternate key, then
30+
* for IaC scenarios, the alternate key should be called `uniqueName`, if there isn't already a more natural existing property that could be used as an alternate key.
31+
* the service must also support `GET` using the alternate key pattern.
3232
* Services should always support `POST` to the collection URL.
3333
* For service-generated keys, this should return the server generated key.
34-
* For client-provided keys, the client should provide the key as part of the request payload.
35-
* If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error.
34+
* For client-provided keys, the client can provide the key as part of the request payload.
35+
* If a service does not support `Upsert`, then a `PATCH` call against a non-existent resource must result in an HTTP "404 not found" error.
3636

3737
This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `Upsert` without impacting existing apps or functionality.
3838

@@ -44,9 +44,12 @@ This pattern should be adopted for resources that are managed through infrastruc
4444

4545
## Issues and considerations
4646

47-
* The addition of this new pattern (with an alternate key) to an existing API does not represent a breaking change.
48-
However, some API producers may have concerns about accidental usages of this new pattern unwittingly creating many new resources when the intent was an update.
49-
As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the Upsert behavior.
47+
* Services with existing APIs that use a client-defined key that want to start supporting the `Upsert` pattern may have concerns about backwards compatibility.
48+
API producers can require clients to opt-in to the `Upsert` pattern, by using the `Prefer: create-if-missing` HTTP request header.
49+
* `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL.
50+
* Services that support `Upsert` should allow clients to use the:
51+
* `If-Match=*` request header to explicitly treat an `Upsert` request as an update and not an insert.
52+
* `If-None-Match=*` request header to explicitly treat an `Upsert` request as an insert and not an update.
5053
* The client-provided alternate key must be immutable after being set. If its value is null then it should be settable as a way to backfill existing resources for use in IaC scenarios.
5154
* API producers could use `PUT` operations to create or update, but generally this approach is not recommended due to the destructive nature of `PUT`'s replace semantics.
5255
* API producers may annotate entity sets, singletons and collections to indicate that entities can be "upserted". The example below shows this annotation for the `groups` entity set.
@@ -61,11 +64,9 @@ As a result, API producers can use the `Prefer: idempotent` to require clients t
6164
</EntitySet>
6265
```
6366

64-
* `Upsert` can also be supported against singletons, using a `PATCH` to the singleton's URL.
65-
6667
## Examples
6768

68-
For these examples we'll use the `group` entity type, which defines both a primary (service-generated) key (`id`) and an alternate (client-provided) key (`uniqueName`).
69+
For these examples we'll use the `group` entity type, which defines both a primary (service-generated) key (`id`) and an alternate (client-provided) key (`uniqueName`).
6970

7071
```xml
7172
<EntityType Name="group">
@@ -99,7 +100,7 @@ Create a new group, with a `uniqueName` of "Group157". In this case, this group
99100

100101
```http
101102
PATCH /groups(uniqueName='Group157')
102-
Prefer: idempotent; return=representation
103+
Prefer: return=representation
103104
```
104105

105106
```json
@@ -113,7 +114,7 @@ Response:
113114

114115
```http
115116
201 created
116-
Preference-Applied: idempotent; return=representation
117+
Preference-Applied: return=representation
117118
```
118119

119120
```json
@@ -131,7 +132,7 @@ Create a new group, with a `uniqueName` of "Group157", exactly like before. Exce
131132

132133
```http
133134
PATCH /groups(uniqueName='Group157')
134-
Prefer: idempotent; return=representation
135+
Prefer: return=representation
135136
```
136137

137138
```json
@@ -145,7 +146,7 @@ Response:
145146

146147
```http
147148
200 ok
148-
Preference-Applied: idempotent; return=representation
149+
Preference-Applied: return=representation
149150
```
150151

151152
```json
@@ -165,7 +166,7 @@ Update "Group157" group with a new description.
165166

166167
```http
167168
PATCH /groups(uniqueName='Group157')
168-
Prefer: idempotent; return=representation
169+
Prefer: return=representation
169170
```
170171

171172
```json
@@ -178,7 +179,7 @@ Response:
178179

179180
```http
180181
200 ok
181-
Preference-Applied: idempotent; return=representation
182+
Preference-Applied: return=representation
182183
```
183184

184185
```json
@@ -190,14 +191,47 @@ Preference-Applied: idempotent; return=representation
190191
}
191192
```
192193

193-
### Upsert not supported
194+
### Upsert opt-in request
195+
196+
In this case, the group API is a pre-existing API that supports `PATCH` with a client-provided alternate key. To enable `Upsert` behavior,
197+
the client must opt-in using an HTTP request header, to create a new group using `PATCH`.
198+
199+
```http
200+
PATCH /groups(uniqueName='Group157')
201+
Prefer: create-if-missing; return=representation
202+
```
203+
204+
```json
205+
{
206+
"displayName": "My favorite group",
207+
"description": "All my favorite people in the world"
208+
}
209+
```
210+
211+
Response:
212+
213+
```http
214+
201 created
215+
Preference-Applied: create-if-missing; return=representation
216+
```
217+
218+
```json
219+
{
220+
"id": "1a89ade6-9f59-4fea-a139-23f84e3aef66",
221+
"displayName": "My favorite group",
222+
"description": "All my favorite people in the world",
223+
"uniqueName": "Group157"
224+
}
225+
```
226+
227+
### Upsert (create) not supported
194228

195-
Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally
196-
the service does not `Upsert` for groups.
229+
Following on from the last example, the same request to create a new group, with a `uniqueName` of "Group157",
230+
without the opt-in header, results in a 404 HTTP response code.
197231

198232
```http
199233
PATCH /groups(uniqueName='Group157')
200-
Prefer: idempotent; return=representation
234+
Prefer: return=representation
201235
```
202236

203237
```json
@@ -210,5 +244,5 @@ Prefer: idempotent; return=representation
210244
Response:
211245

212246
```http
213-
409 conflict
247+
404 not found
214248
```

0 commit comments

Comments
 (0)