Skip to content

Commit dcba8e2

Browse files
committed
Document evolvable enum design pattern
1 parent 0f714c2 commit dcba8e2

File tree

1 file changed

+302
-0
lines changed

1 file changed

+302
-0
lines changed

graph/patterns/evolvable-enums.md

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
# Evolvable Enums
2+
3+
Microsoft Graph API Design Pattern
4+
5+
*The evolvable enum pattern allows API producers to extend enumerated types with new members without breaking API consumers.*
6+
7+
## Problem
8+
9+
---
10+
Frequently API producers want to add new members to an enum type after it is initially published. Some serialization libraries may fail when they encounter members in an enum type that were added after the serialization model was generated. Within this documented we will refer to any added enum members as unknown.
11+
12+
## Solution
13+
14+
---
15+
The solution here is to add a 'sentinel' member named ```unknownFutureValue``` at the end of the currently known enum members. The API producer will then replace any member that is numerically after ```unknownFutureValue``` with ```unknownFutureValue```. If an API consumer can handle unknown enum values the consumer can opt into receiving the unknown enum members by specifying the ```Preference: include-unknown-enum-members``` HTTP Header in their request(s), the API producer will then indicate that this preference has been applied by returning the ```Preference-Applied: include-unknown-enum-member``` HTTP header in the response.
16+
17+
## When to Use this Pattern
18+
19+
---
20+
21+
- It is a best practice to include a ```unknownFutureValue``` value when the enum is initially introduced to allow flexibility to extend the enum during the lifetime of the API. Even if the API producer believes they have included all possible members in an enum it is still strongly recommended to include a ```unknownFutureValue``` member to allow for unforeseen future circumstances which may required extending the enum.
22+
23+
- This pattern must not be used in scenarios where an API consumer wants to use enum members that are not known to the API producer.
24+
25+
## Issues and Considerations
26+
27+
---
28+
29+
An enum member with the name of ```unknownFutureValue``` **must** only be used as a sentinel value, an API producer **must** not include a member named ```unknownFutureValue``` in an enum for any other purpose.
30+
31+
The value (i.e. position) of the ```unknownFutureValue``` sentinel member can only be changed when a new major version of Graph (e.g. 2.0) is released.
32+
33+
Enum Types can have multiple members with the same numeric value to allow for aliasing enum members, ```unknownFutureValue``` **must** not be aliased to any other enum member.
34+
35+
There is no ability for a client to indicate that it can handle a subset of unknown enum members, instead that can only specify either that they can not handle any unknown enum members, or they can handle any unknown enum members.
36+
37+
The ```Preference: include-unknown-enum-members``` header applies to all included enums in the request/response, there is no way for an API consumer to apply the behavior to only a subset of enum types.
38+
39+
New values **must** not be inserted into the enum before ```unknownFutureValue```, implementers are recommended to make the numeric value of ```unknownFutureValue``` one greater than the last known enum member to ensure there are no gaps into which a new member could be inadvertently added. The exception to this is the case of flagged enums in which case the value of ```unknownFutureValue``` should be be next power of 2 value.
40+
41+
For flagged enums care should be exercised to ensure that ```unknownFutureValue``` is not included in any enum members that represent a combination of other enum members.
42+
43+
If an API consumer specifies ```unknownFutureValue``` for the value of a property in a ```POST```/```PUT``` request or as parameter of an action or function the API producer must reject the request with a ```400 Bad Request``` HTTP status.
44+
45+
If an API consumer specifies ```unknownFutureValue``` for the value of a property in a ```PATCH``` request the API producer must treat the property as if it were absent (i.e. the existing value should not be changed). For the case where the API producer treats ```PATCH``` as an upsert the call **must** be rejected with a ```400 Bad Request``` HTTP status.
46+
47+
For details of how the ```unknownFutureValue``` value is handled as part of a ```$filter``` clause please consult the following examples.
48+
49+
### CSDL
50+
51+
```xml
52+
<EntityType Name="example">
53+
<Property Name="enumProperty" Type="exampleEnum"/>
54+
</EntityType>
55+
56+
<EnumType Name="exampleEnum">
57+
<Member Name="default" Value="0"/>
58+
<Member Name="one" Value="1"/>
59+
<Member Name="unknownFutureValue" Value="2"/>
60+
<Member Name="newValue" Value="3"/>
61+
</EnumType>
62+
```
63+
64+
### Filter Behavior
65+
66+
| ```$filter``` clause | ```Preference: include-unknown-enum-members``` Absent | ```Preference: include-unknown-enum-members``` Present |
67+
|---|---|---|
68+
| ```enumProperty eq unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue```| Return nothing |
69+
| ```enumProperty gt unknownFutureValue```| Return entities where enumProperty has any value greater than ```unknownFutureValue``` replacing actual value with ```unknownFutureValue``` | Return entities where enumProperty has any value greater than ```unknownFutureValue``` |
70+
| ```enumProperty lt unknownFutureValue```| Return entities where enumProperty has any known value (i.e. less than ```unknownFutureValue```) | Return entities where enumProperty has any value less than ```unknownFutureValue```|
71+
| ```enumProperty eq newValue``` | ```400 Bad Request``` | Return entities where enumProperty has the value ```newValue``` |
72+
| ```enumProperty gt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value greater than ```newValue``` |
73+
| ```enumProperty lt newValue``` | ```400 Bad Request``` | Return entities where enumProperty has a value less than ```newValue``` |
74+
75+
If an evolvable enum is included in an ```$orderby``` clause the actual numeric value of the member should be used to order the collection, after sorting the member should then be replaced with ```unknownFutureValue``` when the ```Preference: include-unknown-enum-members``` header is absent.
76+
77+
## Examples
78+
79+
---
80+
81+
For the following examples we will consider the ```managedDevice``` entity which refers to the ```managedDeviceArchitecture``` enum type.
82+
83+
```xml
84+
<!-- Simplified entity for example purposes -->
85+
<EntityType Name="managedDevice" BaseType="graph.entity">
86+
<Property Name="displayName" Type="Edm.String" />
87+
<Property Name="processorArchitecture" Type="graph.managedDeviceArchitecture"/>
88+
</EntityType>
89+
```
90+
91+
When the ```managedDeviceArchitecture``` enum was initially published to Graph it was defined as below:
92+
93+
```xml
94+
<!-- Slightly modified enum for example purposes -->
95+
<EnumType Name="managedDeviceArchitecture">
96+
<Member Name="unknown" Value="0"/>
97+
<Member Name="x86" Value="1"/>
98+
<Member Name="x64" Value="2"/>
99+
<Member Name="arm" Value="3"/>
100+
<Member Name="arm64" Value="4"/>
101+
<Member Name="unknownFutureValue" Value="5"/>
102+
</EnumType>
103+
```
104+
105+
The enum was later extended to add a new value of ```quantum``` leading to the below CSDL
106+
107+
```xml
108+
<EnumType Name="managedDeviceArchitecture">
109+
<Member Name="unknown" Value="0"/>
110+
<Member Name="x86" Value="1"/>
111+
<Member Name="x64" Value="2"/>
112+
<Member Name="arm" Value="3"/>
113+
<Member Name="arm64" Value="4"/>
114+
<Member Name="unknownFutureValue" Value="5"/>
115+
<Member Name="quantum" Value="6"/>
116+
</EnumType>
117+
```
118+
119+
### Default Behavior
120+
121+
```http
122+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture
123+
```
124+
125+
```json
126+
{
127+
"value": [
128+
{
129+
"id": "0",
130+
"displayName": "Surface Pro X",
131+
"processorArchitecture" : "arm64"
132+
},
133+
{
134+
"id": "1",
135+
"displayName": "Prototype",
136+
"processorArchitecture": "unknownFutureValue"
137+
}
138+
{
139+
"id": "2",
140+
"displayName": "My Laptop",
141+
"processorArchitecture": "x64"
142+
}
143+
]
144+
}
145+
```
146+
147+
In this case the value of the ```processorArchitecture``` property is ```quantum``` however since the client did not request the ```include-unknown-enum-members``` header the value was replaced with ```unknownFutureValue```
148+
149+
### Include opt-in Header
150+
151+
```http
152+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture
153+
154+
Preference: include-unknown-enum-members
155+
```
156+
157+
```json
158+
{
159+
"value": [
160+
{
161+
"displayName": "Surface Pro X",
162+
"processorArchitecture" : "arm64"
163+
},
164+
{
165+
"displayName": "Prototype",
166+
"processorArchitecture": "quantum"
167+
},
168+
{
169+
"displayName": "My Laptop",
170+
"processorArchitecture": "x64"
171+
}
172+
]
173+
}
174+
```
175+
176+
### Default Sort Behavior
177+
178+
```http
179+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$orderBy=processorArchitecture
180+
```
181+
182+
```json
183+
{
184+
"value": [
185+
{
186+
"displayName": "Surface Pro X",
187+
"processorArchitecture" : "arm64"
188+
},
189+
{
190+
"displayName": "My Laptop",
191+
"processorArchitecture": "x64"
192+
},
193+
{
194+
"displayName": "Prototype",
195+
"processorArchitecture": "unknownFutureValue"
196+
}
197+
]
198+
}
199+
```
200+
201+
### Sort Behavior with opt-in Header
202+
203+
```http
204+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture
205+
206+
Preference: include-unknown-enum-members
207+
```
208+
209+
```json
210+
{
211+
"value": [
212+
{
213+
"displayName": "Surface Pro X",
214+
"processorArchitecture" : "arm64"
215+
},
216+
{
217+
"displayName": "My Laptop",
218+
"processorArchitecture": "x64"
219+
},
220+
{
221+
"displayName": "Prototype",
222+
"processorArchitecture": "quantum"
223+
}
224+
]
225+
}
226+
```
227+
228+
### Default Filter Behavior
229+
230+
```http
231+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64
232+
```
233+
234+
```json
235+
{
236+
"value": [
237+
{
238+
"displayName": "My Laptop",
239+
"processorArchitecture": "x64"
240+
},
241+
{
242+
"displayName": "Prototype",
243+
"processorArchitecture": "unknownFutureValue"
244+
}
245+
]
246+
}
247+
```
248+
249+
### Filter Behavior with opt-in Header
250+
251+
```http
252+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$select=displayName,processorArchitecture&$filter=processorArchitecture gt x64
253+
254+
Preference: include-unknown-enum-members
255+
```
256+
257+
```json
258+
{
259+
"value": [
260+
{
261+
"displayName": "My Laptop",
262+
"processorArchitecture": "x64"
263+
},
264+
{
265+
"displayName": "Prototype",
266+
"processorArchitecture": "quantum"
267+
}
268+
]
269+
}
270+
```
271+
272+
### Patch Example
273+
274+
```http
275+
PATCH https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1
276+
277+
{
278+
"displayName": "Secret Prototype",
279+
"processorArchitecture": "unknownFutureValue"
280+
}
281+
```
282+
283+
```json
284+
{
285+
"id": "1",
286+
"displayName": "Secret Prototype",
287+
"processorArchitecture": "unknownFutureValue"
288+
}
289+
```
290+
291+
```http
292+
GET https://graph.microsoft.com/v1.0/deviceManagement/managedDevices/1
293+
Preference: include-unknown-enum-members
294+
```
295+
296+
```json
297+
{
298+
"id": "1",
299+
"displayName": "Secret Prototype",
300+
"processorArchitecture": "quantum"
301+
}
302+
```

0 commit comments

Comments
 (0)