Skip to content

Commit 2f80abb

Browse files
author
Dan Kershaw [MSFT]
committed
First draft for upsert pattern
1 parent 29b1752 commit 2f80abb

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Infrastructure as code
2+
3+
Microsoft Graph API Design Pattern
4+
5+
*Infrastructure as code (IaC) resource pattern ensures that repeated operations to a resource always results in a predictable resource end-state.*
6+
7+
## Problem
8+
9+
Infrastructure as code (IaC) defines system resources and topologies in a descriptive manner that allows teams to manage those resources as they would code.
10+
Practicing IaC helps teams deploy system resources in a reliable, repeatable, and controlled way.
11+
IaC also helps automate deployment and reduces the risk of human error, especially for complex large environments.
12+
Customers want to adopt IaC practices for many of the resources managed through Microsoft Graph.
13+
14+
Most resources' creation operations in Microsoft Graph are not idempotent in nature.
15+
As a consequence, API consumers that want to offer IaC solutions, must create compensation layers that can mimic idempotent behavior.
16+
For example, when creating a resource, the compensation layer must check whether the resource first exists, before trying to create or update the resource.
17+
18+
Additionally, IaC code scripts or templates usually employ client-provided names (or keys) to track resources in a predictable manner, whereas [Microsoft Graph guidelines](../GuidelinesGraph.md#behavior-modeling) suggests use of `POST` to create new entities with service-generated keys.
19+
20+
## Solution
21+
22+
The solution is to use an `UPSERT` pattern, to solve for the non-idempotent creation and client-provided naming problems.
23+
24+
* For IaC scenarios, resources must use `UPSERT` semantics with an [alternate key](./alternate-key.md):
25+
* Use `PATCH` with a client-provided alternate key.
26+
* For a non-existent resource (specified by the alternate key) the service must handle this as a "create". As part of creation, the service must still generate the primary key value.
27+
* For an existing resource (specified by the alternate key) the service must handle this as an "update.
28+
* Any new alternate key, used for IaC scenarios, be called `uniqueName`.
29+
* NOTE: the service must also support `GET` using the alternate key pattern.
30+
* For consistent CRUD Microsoft Graph behaviors, all resources, **including** resources used in IaC scenarios, should use `POST` and a service-generated primary key, per existing guidelines, and support `GET`, `PATCH` and `DELETE` using the primary key.
31+
* If a service does not support `UPSERT`, then a `PATCH` call against a non-existent resource must result in an HTTP "409 conflict" error.
32+
33+
This solution allows for existing resources that follow Microsoft Graph conventions for CRUD operations to add `UPSERT` without impacting existing apps or functionality.
34+
35+
Ideally, all new entity types should support an `UPSERT` mechanism, especially if the resource is likely be used in IaC scenarios.
36+
37+
## When to use this pattern
38+
39+
This pattern should be adopted for resources that are managed through infrastructure as code or desired state configuration.
40+
41+
## Issues and considerations
42+
43+
* The addition of this new pattern (with alternate key) does not represent a breaking change.
44+
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.
45+
As a result, API producers can use the `Prefer: idempotent` to require clients to opt-in to the UPSERT behavior.
46+
* 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.
47+
* API producers could to use `UPSERT` with a primary (client-provided) key and this may be appropriate for some scenarios. However, the recommendation is for resources to support creation using `POST` and a service-generated primary key, for consistency reasons.
48+
* 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.
49+
50+
```xml
51+
<EntitySet Name="groups" EntityType="microsoft.graph.group">
52+
<Annotation Term="Org.OData.Capabilities.V1.UpdateRestrictions">
53+
<Record>
54+
<PropertyValue Property="Upsertable" Bool="true"/>
55+
</Record>
56+
</Annotation>
57+
</EntitySet>
58+
```
59+
60+
## Examples
61+
62+
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`).
63+
64+
```xml
65+
<EntityType Name="group">
66+
<Key>
67+
<PropertyRef Name="id"/>
68+
</Key>
69+
<Property Name="id" Type="Edm.String"/>
70+
<Property Name="uniqueName" Type="Edm.String"/>
71+
<Property Name="displayName" Type="Edm.String"/>
72+
<Property Name="description" Type="Edm.String"/>
73+
<Annotation Term="OData.Community.Keys.V1.AlternateKeys">
74+
<Collection>
75+
<Record Type="OData.Community.Keys.V1.AlternateKey">
76+
<PropertyValue Property="Key">
77+
<Collection>
78+
<Record Type="OData.Community.Keys.V1.PropertyRef">
79+
<PropertyValue Property="Name" PropertyPath="uniqueName" />
80+
</Record>
81+
</Collection>
82+
</PropertyValue>
83+
</Record>
84+
</Collection>
85+
</Annotation>
86+
</Property>
87+
</EntityType>
88+
```
89+
90+
### Upserting a record (creation path)
91+
92+
Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist.
93+
94+
```http
95+
PATCH /groups(uniqueName='Group157')
96+
```
97+
98+
```json
99+
{
100+
"displayName": "My favorite group",
101+
"description": "All my favorite people in the world"
102+
}
103+
```
104+
105+
Response:
106+
107+
```http
108+
201 created
109+
```
110+
111+
```json
112+
{
113+
"id": "1a89ade6-9f59-4fea-a139-23f84e3aef66",
114+
"displayName": "My favorite group",
115+
"description": "All my favorite people in the world",
116+
"uniqueName": "Group157"
117+
}
118+
```
119+
120+
### Upserting a record (update path)
121+
122+
Create a new group, with a `uniqueName` of "Group157", exactly like before. Except in this case, this group already exists. This is a common scenario in IaC, when a deployment template is re-run multiple times.
123+
124+
```http
125+
PATCH /groups(uniqueName='Group157')
126+
```
127+
128+
```json
129+
{
130+
"displayName": "My favorite group",
131+
"description": "All my favorite people in the world"
132+
}
133+
```
134+
135+
Response:
136+
137+
```http
138+
200 ok
139+
```
140+
141+
```json
142+
{
143+
"id": "1a89ade6-9f59-4fea-a139-23f84e3aef66",
144+
"displayName": "My favorite group",
145+
"description": "All my favorite people in the world",
146+
"uniqueName": "Group157"
147+
}
148+
```
149+
150+
Notice how this operation is idempotent in nature, rather than returning a 409 conflict error.
151+
152+
### Upsert not supported
153+
154+
Create a new group, with a `uniqueName` of "Group157". In this case, this group does not exist and additionally
155+
the service does not `UPSERT` for groups.
156+
157+
```http
158+
PATCH /groups(uniqueName='Group157')
159+
```
160+
161+
```json
162+
{
163+
"displayName": "My favorite group",
164+
"description": "All my favorite people in the world"
165+
}
166+
```
167+
168+
Response:
169+
170+
```http
171+
409 conflict
172+
```

0 commit comments

Comments
 (0)