|
| 1 | +# Change Tracking |
| 2 | + |
| 3 | +Microsoft Graph API Design Pattern |
| 4 | + |
| 5 | +*The change tracking pattern provides the ability to keep API consumers in sync with changes in Microsoft Graph without having to continuously poll the API.* |
| 6 | + |
| 7 | +## Problem |
| 8 | +--------- |
| 9 | + |
| 10 | +API consumers require an efficient way to keep data in sync with Microsoft Graph, and the API design should be optimized to avoid polling as it is costly for consumers and producers alike as well as wouldn't guarantee data integrity. |
| 11 | + |
| 12 | +## Solution |
| 13 | +-------- |
| 14 | + |
| 15 | +API designers can enable the change tracking (delta) capability on entity collections by declaring a delta function for API consumers to use to track changes in that collection. |
| 16 | + |
| 17 | +This new endpoint can be used to sync API consumers. This is achieved through returning a delta link with a watermark. Once the API consumer needs to refresh the data it uses the last provided delta link to catch up on new changes since their last request. Delta guarantees integrity of data through the watermark, regardless of service partitions and other obscure aspects for clients. |
| 18 | + |
| 19 | +> Note: although this capability is similar to the [OData $delta feed](https://docs.oasis-open.org/odata/odata-json-format/v4.0/errata02/os/odata-json-format-v4.0-errata02-os-complete.html#_Toc403940644) capability, it is a different construct. Microsoft Graph APIs MUST provide change tracking through the delta function and MUST NOT implement the OData $delta feed when providing change tracking capabilities to ensure the uniformity of the API experience. |
| 20 | +
|
| 21 | +## Issues and Considerations |
| 22 | +------------------------- |
| 23 | + |
| 24 | +Implementer MUST implement a watermark storage system in case of active watermarks. Passive watermarks are watermarks that can be retrieved from the context (e.g. timestamp), active watermarks represent information required to track the sync state which cannot be retrieved from the context (e.g. cursor from data store, partition affinity marker, partition id, generated unique sync identifier...) |
| 25 | + |
| 26 | +Implementer MUST implement soft deletion for entities in the backend storage system. The soft deletion will provide useful information to the client to appropriately reflect deletions. |
| 27 | + |
| 28 | +When an entity is soft deleted, the delta function MUST return the id of the deleted entity as well as a `@removed` annotation with the `reason` field. |
| 29 | +- The reason MUST be set to `changed` if the entity can be restored. `"@removed": {"reason": "changed"}`. |
| 30 | +- The reason MUST be set to `deleted` if the entity cannot be restored. `"@removed": {"reason": "deleted"}`. |
| 31 | + |
| 32 | +When a link to an entity is deleted, or when the linked entity is deleted, or when a link to an entity is added, implementer MUST return a `property@delta` annotation. e.g. considering the entity Group has a navigation property named members of type Collection(user): |
| 33 | + |
| 34 | +- When a user is added to the group `"members@delta": [{ "@odata.type": "#microsoft.graph.user", "id of the added user"}]` |
| 35 | +- When a user is removed from the group, or the target user is deleted `"members@delta": [{"@removed": {"reason": "deleted"}, "id of the deleted or removed user"}]` |
| 36 | + |
| 37 | +> Note: the delta function also provides support for $filter and $select to allow the API consumer to narrow down the number of entities and properties retrieved as well as the number of changes that are tracked. Additionally the delta function can also support $top to allow the API consumer to sync smaller sets of changes as well as $expand to allow the API consumer to sync related data. Expand across workloads is not supported today however. |
| 38 | +
|
| 39 | +## When to Use this Pattern |
| 40 | +------------------------ |
| 41 | + |
| 42 | +Before using the change tracking pattern in your API definition, make sure your scenario fits the following criteria: |
| 43 | + |
| 44 | +- API consumers want to sync the data. |
| 45 | +- API consumers don't want to be immediately notified of changes. (see change notifications pattern for this scenario). |
| 46 | +- API consumers are not looking for a "one-time" export or back-up mechanism. |
| 47 | + |
| 48 | +### Alternatives |
| 49 | + |
| 50 | +- Change notifications pattern (TODO add link when described) |
| 51 | +- Backup pattern (TODO) |
| 52 | + |
| 53 | +## Examples |
| 54 | +------- |
| 55 | + |
| 56 | +### Getting changes for the users entity set |
| 57 | + |
| 58 | +```HTTP |
| 59 | +GET https://graph.microsoft.com/v1.0/users/delta |
| 60 | +``` |
| 61 | + |
| 62 | +```json |
| 63 | +{ |
| 64 | + "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users", |
| 65 | + "@odata.deltaLink": "https://graph.microsoft.com/v1.0/users/delta?$deltatoken=mS5DuRZGjVL-abreviated", |
| 66 | + "value": [ |
| 67 | + { |
| 68 | + "businessPhones": [ |
| 69 | + "+1 309 555 0104" |
| 70 | + ], |
| 71 | + "displayName": "Grady Archie", |
| 72 | + "givenName": "Grady", |
| 73 | + "jobTitle": "Designer", |
| 74 | + |
| 75 | + "officeLocation": "19/2109", |
| 76 | + "preferredLanguage": "en-US", |
| 77 | + "surname": "Archie", |
| 78 | + "userPrincipalName": "[email protected]", |
| 79 | + "id": "0baaae0f-b0b3-4645-867d-742d8fb669a2", |
| 80 | + "manager@delta": [ |
| 81 | + { |
| 82 | + "@odata.type": "#microsoft.graph.user", |
| 83 | + "id": "99789584-a1e1-4232-90e5-866170e3d4e7" |
| 84 | + } |
| 85 | + ] |
| 86 | + }, |
| 87 | + { |
| 88 | + "id": "0bbbbb0f-b0b3-4645-867d-742d8fb669a2", |
| 89 | + "@removed": { |
| 90 | + "reason": "changed" |
| 91 | + } |
| 92 | + } |
| 93 | + ] |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +> Note: the response contains an `@odata.deltaLink` instance annotation with the watermark only when all the changes are enumerated. If more changes need to be enumerated, the response instead contains an `@odata.nextLink` instance annotation the application can request right away to get the next page. |
| 98 | +
|
| 99 | +### CSDL example |
| 100 | + |
| 101 | +```xml |
| 102 | +<Function Name="delta" IsBound="true"> |
| 103 | + <Parameter Name="bindingParameter" Type="Collection(Microsoft.DirectoryServices.user)" /> |
| 104 | + <ReturnType Type="Collection(Microsoft.DirectoryServices.user)" /> |
| 105 | +</Function> |
| 106 | + |
| 107 | +<EntitySet Name="users" EntityType="microsoft.graph.user"> |
| 108 | + <Annotation Term="Org.OData.Capabilities.V1.ChangeTracking"> |
| 109 | + <Record> |
| 110 | + <PropertyValue Property="Supported" Bool="true" /> |
| 111 | + </Record> |
| 112 | + </Annotation> |
| 113 | +</EntitySet> |
| 114 | +``` |
0 commit comments